mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
feat(runner): V8 extractor overrides + V8 action source for WebDriver
When the driver implements WebDriver, the runner sources extractor values from V8 (real DOM) and the next action from the V8-side action generator. LTL property predicates still run host-side in goja.
This commit is contained in:
1 parent
a31d9869b1
commit
3aa6745f97
1 file changed
+76
-1
@@ -90,11 +90,21 @@ func Run(ctx context.Context, options Options) (Summary, error) {
|
|||||||
logs = collectLogs(ctx, options.Driver, logSince)
|
logs = collectLogs(ctx, options.Driver, logSince)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
var v8Overrides map[int]json.RawMessage
|
||||||
if web, ok := options.Driver.(driver.WebDriver); ok {
|
if web, ok := options.Driver.(driver.WebDriver); ok {
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
htmlCaptured = captureHTML(ctx, web, options.TraceWriter, logger, si, false)
|
htmlCaptured = captureHTML(ctx, web, options.TraceWriter, logger, si, false)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
g.Go(func() error {
|
||||||
|
overrides, err := web.EvaluateExtractors(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("v8 extractor evaluation failed", "step", si, "err", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
v8Overrides = overrides
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if pendingPostScreenshot {
|
if pendingPostScreenshot {
|
||||||
postStep := pendingPostScreenshotStep
|
postStep := pendingPostScreenshotStep
|
||||||
@@ -133,6 +143,9 @@ func Run(ctx context.Context, options Options) (Summary, error) {
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return summary, fmt.Errorf("step %d push: %w", stepIndex, err)
|
return summary, fmt.Errorf("step %d push: %w", stepIndex, err)
|
||||||
}
|
}
|
||||||
|
if err := options.Verifier.OverrideExtractorValues(v8Overrides); err != nil {
|
||||||
|
logger.Warn("v8 override apply failed", "step", stepIndex, "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
screen := ""
|
screen := ""
|
||||||
if tree != nil && len(tree.Elements) > 0 {
|
if tree != nil && len(tree.Elements) > 0 {
|
||||||
@@ -147,7 +160,13 @@ func Run(ctx context.Context, options Options) (Summary, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextAction, nextErr := options.Verifier.NextAction()
|
var nextAction verifier.Action
|
||||||
|
var nextErr error
|
||||||
|
if web, ok := options.Driver.(driver.WebDriver); ok {
|
||||||
|
nextAction, nextErr = nextActionFromV8(ctx, web)
|
||||||
|
} else {
|
||||||
|
nextAction, nextErr = options.Verifier.NextAction()
|
||||||
|
}
|
||||||
var traceAction *trace.Action
|
var traceAction *trace.Action
|
||||||
if nextErr == nil {
|
if nextErr == nil {
|
||||||
traceAction = traceActionFor(nextAction, tree)
|
traceAction = traceActionFor(nextAction, tree)
|
||||||
@@ -414,6 +433,62 @@ func captureMetrics(ctx context.Context, options Options, logger *slog.Logger, s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nextActionFromV8 invokes the V8-side action generator and decodes the
|
||||||
|
// resulting JSON into a verifier.Action. ErrNoAction is returned when the
|
||||||
|
// generator declined to act this tick.
|
||||||
|
func nextActionFromV8(ctx context.Context, web driver.WebDriver) (verifier.Action, error) {
|
||||||
|
raw, err := web.NextActionFromV8(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return verifier.Action{}, fmt.Errorf("v8 next action: %w", err)
|
||||||
|
}
|
||||||
|
if len(raw) == 0 || string(raw) == "null" {
|
||||||
|
return verifier.Action{}, verifier.ErrNoAction
|
||||||
|
}
|
||||||
|
var decoded struct {
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
X int `json:"x"`
|
||||||
|
Y int `json:"y"`
|
||||||
|
FromX int `json:"from_x"`
|
||||||
|
FromY int `json:"from_y"`
|
||||||
|
ToX int `json:"to_x"`
|
||||||
|
ToY int `json:"to_y"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
DurationMillis int `json:"duration_millis"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(raw, &decoded); err != nil {
|
||||||
|
return verifier.Action{}, fmt.Errorf("decode v8 action: %w", err)
|
||||||
|
}
|
||||||
|
switch decoded.Kind {
|
||||||
|
case "Tap":
|
||||||
|
if decoded.X == 0 && decoded.Y == 0 {
|
||||||
|
return verifier.Action{}, verifier.ErrNoAction
|
||||||
|
}
|
||||||
|
return verifier.Action{Kind: verifier.ActionKindTap, X: decoded.X, Y: decoded.Y}, nil
|
||||||
|
case "InputText":
|
||||||
|
return verifier.Action{
|
||||||
|
Kind: verifier.ActionKindInputText,
|
||||||
|
X: decoded.X, Y: decoded.Y,
|
||||||
|
Text: decoded.Text,
|
||||||
|
}, nil
|
||||||
|
case "Swipe":
|
||||||
|
return verifier.Action{
|
||||||
|
Kind: verifier.ActionKindSwipe,
|
||||||
|
FromX: decoded.FromX,
|
||||||
|
FromY: decoded.FromY,
|
||||||
|
ToX: decoded.ToX,
|
||||||
|
ToY: decoded.ToY,
|
||||||
|
DurationMillis: decoded.DurationMillis,
|
||||||
|
}, nil
|
||||||
|
case "PressKey":
|
||||||
|
return verifier.Action{Kind: verifier.ActionKindPressKey, Key: decoded.Key}, nil
|
||||||
|
case "Wait":
|
||||||
|
return verifier.Action{Kind: verifier.ActionKindWait, DurationMillis: decoded.DurationMillis}, nil
|
||||||
|
default:
|
||||||
|
return verifier.Action{}, verifier.ErrNoAction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// captureHTML pulls the current document HTML from a WebDriver-capable
|
// captureHTML pulls the current document HTML from a WebDriver-capable
|
||||||
// driver and stamps it into the trace under html/. Returns true on a
|
// driver and stamps it into the trace under html/. Returns true on a
|
||||||
// successful non-empty write so the Step.HTMLAvailable flag advertises the
|
// successful non-empty write so the Step.HTMLAvailable flag advertises the
|
||||||
|
|||||||
Reference in new issue
Block a user