File size: 4,186 Bytes
6a7089a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | package semantic
import "strings"
// FailureType classifies the cause of an action failure so the recovery
// engine can decide whether and how to attempt self-healing.
type FailureType int
const (
// FailureUnknown β the error could not be classified; not recoverable.
FailureUnknown FailureType = iota
// FailureElementNotFound β the ref no longer maps to a DOM node.
// Recoverable via semantic re-match on a fresh snapshot.
FailureElementNotFound
// FailureElementStale β the node exists but has been detached from
// the live DOM (SPA re-render, modal overlay, etc.).
FailureElementStale
// FailureElementNotInteractable β the element is hidden, disabled,
// or covered by an overlay and cannot receive input.
FailureElementNotInteractable
// FailureNavigation β the action triggered a page navigation; the
// entire snapshot is invalid. Recoverable via fresh snapshot.
FailureNavigation
// FailureNetwork β a transport or Chrome-level error; not recoverable
// by the recovery engine.
FailureNetwork
)
// String returns a human-readable label for the failure type.
func (f FailureType) String() string {
switch f {
case FailureElementNotFound:
return "element_not_found"
case FailureElementStale:
return "element_stale"
case FailureElementNotInteractable:
return "element_not_interactable"
case FailureNavigation:
return "navigation"
case FailureNetwork:
return "network"
default:
return "unknown"
}
}
// Recoverable reports whether the failure type is eligible for automatic
// self-healing. Network and unknown failures are not recoverable.
func (f FailureType) Recoverable() bool {
switch f {
case FailureElementNotFound,
FailureElementStale,
FailureElementNotInteractable,
FailureNavigation:
return true
default:
return false
}
}
// ClassifyFailure inspects an error string and returns the most likely
// FailureType. The classification is intentionally broad β it matches
// error messages produced by Chrome DevTools Protocol, chromedp, and
// PinchTab's own bridge layer so that recovery works regardless of which
// layer reported the failure.
func ClassifyFailure(err error) FailureType {
if err == nil {
return FailureUnknown
}
e := strings.ToLower(err.Error())
// Element not found patterns.
notFoundPatterns := []string{
"could not find node",
"node with given id",
"no node",
"ref not found",
"node not found",
"backend node id",
"no node with given",
}
for _, p := range notFoundPatterns {
if strings.Contains(e, p) {
return FailureElementNotFound
}
}
// Stale element / detached DOM patterns.
// NOTE: "frame" detachment is classified as navigation, not stale.
stalePatterns := []string{
"stale",
"orphan",
"object reference",
"node is detached",
"execution context was destroyed",
"context was destroyed",
"target closed",
}
for _, p := range stalePatterns {
if strings.Contains(e, p) {
return FailureElementStale
}
}
// "detached" alone is stale, but "frame" + "detached" is navigation.
if strings.Contains(e, "detached") && !strings.Contains(e, "frame") {
return FailureElementStale
}
// Not interactable patterns.
interactablePatterns := []string{
"not interactable",
"not clickable",
"element is not visible",
"not visible",
"element is disabled",
"overlapped",
"overlapping",
"obscured",
"pointer-events: none",
"cannot focus",
"outside of the viewport",
"outside the viewport",
}
for _, p := range interactablePatterns {
if strings.Contains(e, p) {
return FailureElementNotInteractable
}
}
// Navigation patterns.
navPatterns := []string{
"navigat",
"page crashed",
"page was destroyed",
"inspected target",
"frame was detached",
"frame detached",
}
for _, p := range navPatterns {
if strings.Contains(e, p) {
return FailureNavigation
}
}
// Network patterns.
networkPatterns := []string{
"net::",
"connection refused",
"could not connect",
"timeout",
"deadline exceeded",
"websocket",
"eof",
"broken pipe",
}
for _, p := range networkPatterns {
if strings.Contains(e, p) {
return FailureNetwork
}
}
return FailureUnknown
}
|