donobu 2.15.1 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/control-panel.js +30 -3
- package/dist/assets/generated/parameter-schemas.json +33 -33
- package/dist/assets/generated/version +1 -1
- package/dist/assets/interactive-elements-tracker.js +351 -0
- package/dist/assets/page-interactions-tracker.js +39 -20
- package/dist/assets/smart-selector-generator.js +38 -15
- package/dist/bindings/FocusPage.d.ts +6 -1
- package/dist/bindings/FocusPage.d.ts.map +1 -1
- package/dist/bindings/FocusPage.js +2 -8
- package/dist/bindings/FocusPage.js.map +1 -1
- package/dist/bindings/PageInteractionTracker.d.ts +9 -2
- package/dist/bindings/PageInteractionTracker.d.ts.map +1 -1
- package/dist/bindings/PageInteractionTracker.js +27 -3
- package/dist/bindings/PageInteractionTracker.js.map +1 -1
- package/dist/bindings/ProposeToolCall.d.ts +20 -0
- package/dist/bindings/ProposeToolCall.d.ts.map +1 -0
- package/dist/bindings/ProposeToolCall.js +29 -0
- package/dist/bindings/ProposeToolCall.js.map +1 -0
- package/dist/bindings/ToggleDonobuAnnotations.d.ts +5 -2
- package/dist/bindings/ToggleDonobuAnnotations.d.ts.map +1 -1
- package/dist/bindings/ToggleDonobuAnnotations.js +12 -6
- package/dist/bindings/ToggleDonobuAnnotations.js.map +1 -1
- package/dist/esm/assets/control-panel.js +30 -3
- package/dist/esm/assets/generated/parameter-schemas.json +33 -33
- package/dist/esm/assets/generated/version +1 -1
- package/dist/esm/assets/interactive-elements-tracker.js +351 -0
- package/dist/esm/assets/page-interactions-tracker.js +39 -20
- package/dist/esm/assets/smart-selector-generator.js +38 -15
- package/dist/esm/bindings/FocusPage.d.ts +6 -1
- package/dist/esm/bindings/FocusPage.d.ts.map +1 -1
- package/dist/esm/bindings/FocusPage.js +2 -8
- package/dist/esm/bindings/FocusPage.js.map +1 -1
- package/dist/esm/bindings/PageInteractionTracker.d.ts +9 -2
- package/dist/esm/bindings/PageInteractionTracker.d.ts.map +1 -1
- package/dist/esm/bindings/PageInteractionTracker.js +27 -3
- package/dist/esm/bindings/PageInteractionTracker.js.map +1 -1
- package/dist/esm/bindings/ProposeToolCall.d.ts +20 -0
- package/dist/esm/bindings/ProposeToolCall.d.ts.map +1 -0
- package/dist/esm/bindings/ProposeToolCall.js +29 -0
- package/dist/esm/bindings/ProposeToolCall.js.map +1 -0
- package/dist/esm/bindings/ToggleDonobuAnnotations.d.ts +5 -2
- package/dist/esm/bindings/ToggleDonobuAnnotations.d.ts.map +1 -1
- package/dist/esm/bindings/ToggleDonobuAnnotations.js +12 -6
- package/dist/esm/bindings/ToggleDonobuAnnotations.js.map +1 -1
- package/dist/esm/managers/CodeGenerator.d.ts.map +1 -1
- package/dist/esm/managers/CodeGenerator.js +2 -1
- package/dist/esm/managers/CodeGenerator.js.map +1 -1
- package/dist/esm/managers/ControlPanel.d.ts +124 -0
- package/dist/esm/managers/ControlPanel.d.ts.map +1 -0
- package/dist/esm/managers/ControlPanel.js +282 -0
- package/dist/esm/managers/ControlPanel.js.map +1 -0
- package/dist/esm/managers/DonobuFlow.d.ts +4 -12
- package/dist/esm/managers/DonobuFlow.d.ts.map +1 -1
- package/dist/esm/managers/DonobuFlow.js +35 -140
- package/dist/esm/managers/DonobuFlow.js.map +1 -1
- package/dist/esm/managers/InteractionVisualizer.d.ts +9 -15
- package/dist/esm/managers/InteractionVisualizer.d.ts.map +1 -1
- package/dist/esm/managers/InteractionVisualizer.js +44 -44
- package/dist/esm/managers/InteractionVisualizer.js.map +1 -1
- package/dist/esm/managers/PageInspector.d.ts +242 -0
- package/dist/esm/managers/PageInspector.d.ts.map +1 -0
- package/dist/esm/managers/PageInspector.js +783 -0
- package/dist/esm/managers/PageInspector.js.map +1 -0
- package/dist/esm/managers/ToolManager.d.ts.map +1 -1
- package/dist/esm/managers/ToolManager.js +3 -5
- package/dist/esm/managers/ToolManager.js.map +1 -1
- package/dist/esm/models/InteractableElement.d.ts +2 -2
- package/dist/esm/models/ToolCallContext.d.ts +6 -2
- package/dist/esm/models/ToolCallContext.d.ts.map +1 -1
- package/dist/esm/playwrightTestExtensions.d.ts +5 -0
- package/dist/esm/playwrightTestExtensions.d.ts.map +1 -1
- package/dist/esm/playwrightTestExtensions.js +52 -13
- package/dist/esm/playwrightTestExtensions.js.map +1 -1
- package/dist/esm/tools/AnalyzePageTextTool.js +1 -1
- package/dist/esm/tools/AnalyzePageTextTool.js.map +1 -1
- package/dist/esm/tools/AssertPageTextTool.js +1 -1
- package/dist/esm/tools/AssertPageTextTool.js.map +1 -1
- package/dist/esm/tools/AssertTool.d.ts.map +1 -1
- package/dist/esm/tools/AssertTool.js +4 -4
- package/dist/esm/tools/AssertTool.js.map +1 -1
- package/dist/esm/tools/ChangeWebBrowserTabTool.d.ts.map +1 -1
- package/dist/esm/tools/ChangeWebBrowserTabTool.js +8 -7
- package/dist/esm/tools/ChangeWebBrowserTabTool.js.map +1 -1
- package/dist/esm/tools/ClickTool.js +1 -1
- package/dist/esm/tools/ClickTool.js.map +1 -1
- package/dist/esm/tools/CreateBrowserCookieReportTool.js +1 -1
- package/dist/esm/tools/CreateBrowserCookieReportTool.js.map +1 -1
- package/dist/esm/tools/CustomToolRunnerTool.d.ts.map +1 -1
- package/dist/esm/tools/CustomToolRunnerTool.js +1 -1
- package/dist/esm/tools/CustomToolRunnerTool.js.map +1 -1
- package/dist/esm/tools/DetectBrokenLinksTool.js +1 -1
- package/dist/esm/tools/DetectBrokenLinksTool.js.map +1 -1
- package/dist/esm/tools/ExtractGoogleStreetviewEntityDataTool.js +1 -1
- package/dist/esm/tools/ExtractGoogleStreetviewEntityDataTool.js.map +1 -1
- package/dist/esm/tools/ExtractPaymentProviderKeyTool.js +1 -1
- package/dist/esm/tools/ExtractPaymentProviderKeyTool.js.map +1 -1
- package/dist/esm/tools/ExtractPublicFacebookEntityDataTool.js +1 -1
- package/dist/esm/tools/ExtractPublicFacebookEntityDataTool.js.map +1 -1
- package/dist/esm/tools/GoForwardOrBackTool.js +1 -1
- package/dist/esm/tools/GoForwardOrBackTool.js.map +1 -1
- package/dist/esm/tools/GoToGoogleMapsStreetViewTool.js +1 -1
- package/dist/esm/tools/GoToGoogleMapsStreetViewTool.js.map +1 -1
- package/dist/esm/tools/GoToWebpageTool.js +1 -1
- package/dist/esm/tools/GoToWebpageTool.js.map +1 -1
- package/dist/esm/tools/HoverOverElementTool.d.ts.map +1 -1
- package/dist/esm/tools/HoverOverElementTool.js +1 -1
- package/dist/esm/tools/HoverOverElementTool.js.map +1 -1
- package/dist/esm/tools/NavigateWithinStreetView.js +1 -1
- package/dist/esm/tools/NavigateWithinStreetView.js.map +1 -1
- package/dist/esm/tools/PressKeyTool.js +1 -1
- package/dist/esm/tools/PressKeyTool.js.map +1 -1
- package/dist/esm/tools/ReloadPageTool.js +1 -1
- package/dist/esm/tools/ReloadPageTool.js.map +1 -1
- package/dist/esm/tools/ReplayableInteraction.d.ts +16 -1
- package/dist/esm/tools/ReplayableInteraction.d.ts.map +1 -1
- package/dist/esm/tools/ReplayableInteraction.js +169 -16
- package/dist/esm/tools/ReplayableInteraction.js.map +1 -1
- package/dist/esm/tools/RunAccessibilityTestTool.d.ts +8 -0
- package/dist/esm/tools/RunAccessibilityTestTool.d.ts.map +1 -1
- package/dist/esm/tools/RunAccessibilityTestTool.js +15 -2
- package/dist/esm/tools/RunAccessibilityTestTool.js.map +1 -1
- package/dist/esm/tools/RunInlineJavaScriptCodeTool.d.ts.map +1 -1
- package/dist/esm/tools/RunInlineJavaScriptCodeTool.js +1 -1
- package/dist/esm/tools/RunInlineJavaScriptCodeTool.js.map +1 -1
- package/dist/esm/tools/SaveWebpageAsPdfTool.js +1 -1
- package/dist/esm/tools/SaveWebpageAsPdfTool.js.map +1 -1
- package/dist/esm/tools/ScrollPageTool.d.ts +1 -1
- package/dist/esm/tools/ScrollPageTool.d.ts.map +1 -1
- package/dist/esm/tools/ScrollPageTool.js +47 -4
- package/dist/esm/tools/ScrollPageTool.js.map +1 -1
- package/dist/esm/tools/WaitTool.js +1 -1
- package/dist/esm/tools/WaitTool.js.map +1 -1
- package/dist/esm/utils/MiscUtils.d.ts +2 -2
- package/dist/esm/utils/MiscUtils.js +2 -2
- package/dist/esm/utils/PlaywrightUtils.d.ts +26 -127
- package/dist/esm/utils/PlaywrightUtils.d.ts.map +1 -1
- package/dist/esm/utils/PlaywrightUtils.js +133 -935
- package/dist/esm/utils/PlaywrightUtils.js.map +1 -1
- package/dist/managers/CodeGenerator.d.ts.map +1 -1
- package/dist/managers/CodeGenerator.js +2 -1
- package/dist/managers/CodeGenerator.js.map +1 -1
- package/dist/managers/ControlPanel.d.ts +124 -0
- package/dist/managers/ControlPanel.d.ts.map +1 -0
- package/dist/managers/ControlPanel.js +282 -0
- package/dist/managers/ControlPanel.js.map +1 -0
- package/dist/managers/DonobuFlow.d.ts +4 -12
- package/dist/managers/DonobuFlow.d.ts.map +1 -1
- package/dist/managers/DonobuFlow.js +35 -140
- package/dist/managers/DonobuFlow.js.map +1 -1
- package/dist/managers/InteractionVisualizer.d.ts +9 -15
- package/dist/managers/InteractionVisualizer.d.ts.map +1 -1
- package/dist/managers/InteractionVisualizer.js +44 -44
- package/dist/managers/InteractionVisualizer.js.map +1 -1
- package/dist/managers/PageInspector.d.ts +242 -0
- package/dist/managers/PageInspector.d.ts.map +1 -0
- package/dist/managers/PageInspector.js +783 -0
- package/dist/managers/PageInspector.js.map +1 -0
- package/dist/managers/ToolManager.d.ts.map +1 -1
- package/dist/managers/ToolManager.js +3 -5
- package/dist/managers/ToolManager.js.map +1 -1
- package/dist/models/InteractableElement.d.ts +2 -2
- package/dist/models/ToolCallContext.d.ts +6 -2
- package/dist/models/ToolCallContext.d.ts.map +1 -1
- package/dist/playwrightTestExtensions.d.ts +5 -0
- package/dist/playwrightTestExtensions.d.ts.map +1 -1
- package/dist/playwrightTestExtensions.js +52 -13
- package/dist/playwrightTestExtensions.js.map +1 -1
- package/dist/tools/AnalyzePageTextTool.js +1 -1
- package/dist/tools/AnalyzePageTextTool.js.map +1 -1
- package/dist/tools/AssertPageTextTool.js +1 -1
- package/dist/tools/AssertPageTextTool.js.map +1 -1
- package/dist/tools/AssertTool.d.ts.map +1 -1
- package/dist/tools/AssertTool.js +4 -4
- package/dist/tools/AssertTool.js.map +1 -1
- package/dist/tools/ChangeWebBrowserTabTool.d.ts.map +1 -1
- package/dist/tools/ChangeWebBrowserTabTool.js +8 -7
- package/dist/tools/ChangeWebBrowserTabTool.js.map +1 -1
- package/dist/tools/ClickTool.js +1 -1
- package/dist/tools/ClickTool.js.map +1 -1
- package/dist/tools/CreateBrowserCookieReportTool.js +1 -1
- package/dist/tools/CreateBrowserCookieReportTool.js.map +1 -1
- package/dist/tools/CustomToolRunnerTool.d.ts.map +1 -1
- package/dist/tools/CustomToolRunnerTool.js +1 -1
- package/dist/tools/CustomToolRunnerTool.js.map +1 -1
- package/dist/tools/DetectBrokenLinksTool.js +1 -1
- package/dist/tools/DetectBrokenLinksTool.js.map +1 -1
- package/dist/tools/ExtractGoogleStreetviewEntityDataTool.js +1 -1
- package/dist/tools/ExtractGoogleStreetviewEntityDataTool.js.map +1 -1
- package/dist/tools/ExtractPaymentProviderKeyTool.js +1 -1
- package/dist/tools/ExtractPaymentProviderKeyTool.js.map +1 -1
- package/dist/tools/ExtractPublicFacebookEntityDataTool.js +1 -1
- package/dist/tools/ExtractPublicFacebookEntityDataTool.js.map +1 -1
- package/dist/tools/GoForwardOrBackTool.js +1 -1
- package/dist/tools/GoForwardOrBackTool.js.map +1 -1
- package/dist/tools/GoToGoogleMapsStreetViewTool.js +1 -1
- package/dist/tools/GoToGoogleMapsStreetViewTool.js.map +1 -1
- package/dist/tools/GoToWebpageTool.js +1 -1
- package/dist/tools/GoToWebpageTool.js.map +1 -1
- package/dist/tools/HoverOverElementTool.d.ts.map +1 -1
- package/dist/tools/HoverOverElementTool.js +1 -1
- package/dist/tools/HoverOverElementTool.js.map +1 -1
- package/dist/tools/NavigateWithinStreetView.js +1 -1
- package/dist/tools/NavigateWithinStreetView.js.map +1 -1
- package/dist/tools/PressKeyTool.js +1 -1
- package/dist/tools/PressKeyTool.js.map +1 -1
- package/dist/tools/ReloadPageTool.js +1 -1
- package/dist/tools/ReloadPageTool.js.map +1 -1
- package/dist/tools/ReplayableInteraction.d.ts +16 -1
- package/dist/tools/ReplayableInteraction.d.ts.map +1 -1
- package/dist/tools/ReplayableInteraction.js +169 -16
- package/dist/tools/ReplayableInteraction.js.map +1 -1
- package/dist/tools/RunAccessibilityTestTool.d.ts +8 -0
- package/dist/tools/RunAccessibilityTestTool.d.ts.map +1 -1
- package/dist/tools/RunAccessibilityTestTool.js +15 -2
- package/dist/tools/RunAccessibilityTestTool.js.map +1 -1
- package/dist/tools/RunInlineJavaScriptCodeTool.d.ts.map +1 -1
- package/dist/tools/RunInlineJavaScriptCodeTool.js +1 -1
- package/dist/tools/RunInlineJavaScriptCodeTool.js.map +1 -1
- package/dist/tools/SaveWebpageAsPdfTool.js +1 -1
- package/dist/tools/SaveWebpageAsPdfTool.js.map +1 -1
- package/dist/tools/ScrollPageTool.d.ts +1 -1
- package/dist/tools/ScrollPageTool.d.ts.map +1 -1
- package/dist/tools/ScrollPageTool.js +47 -4
- package/dist/tools/ScrollPageTool.js.map +1 -1
- package/dist/tools/WaitTool.js +1 -1
- package/dist/tools/WaitTool.js.map +1 -1
- package/dist/utils/MiscUtils.d.ts +2 -2
- package/dist/utils/MiscUtils.js +2 -2
- package/dist/utils/PlaywrightUtils.d.ts +26 -127
- package/dist/utils/PlaywrightUtils.d.ts.map +1 -1
- package/dist/utils/PlaywrightUtils.js +133 -935
- package/dist/utils/PlaywrightUtils.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/clickable-elements-tracker.js +0 -63
- package/dist/assets/zaru-eyes.html +0 -141
- package/dist/esm/assets/clickable-elements-tracker.js +0 -63
- package/dist/esm/assets/zaru-eyes.html +0 -141
- package/dist/esm/managers/TemplateInterpolator.d.ts +0 -19
- package/dist/esm/managers/TemplateInterpolator.d.ts.map +0 -1
- package/dist/esm/managers/TemplateInterpolator.js +0 -86
- package/dist/esm/managers/TemplateInterpolator.js.map +0 -1
- package/dist/esm/managers/ToolTipper.d.ts +0 -32
- package/dist/esm/managers/ToolTipper.d.ts.map +0 -1
- package/dist/esm/managers/ToolTipper.js +0 -229
- package/dist/esm/managers/ToolTipper.js.map +0 -1
- package/dist/esm/models/BrowserState.d.ts +0 -26
- package/dist/esm/models/BrowserState.d.ts.map +0 -1
- package/dist/esm/models/BrowserState.js +0 -3
- package/dist/esm/models/BrowserState.js.map +0 -1
- package/dist/esm/models/ResolverContext.d.ts +0 -7
- package/dist/esm/models/ResolverContext.d.ts.map +0 -1
- package/dist/esm/models/ResolverContext.js +0 -3
- package/dist/esm/models/ResolverContext.js.map +0 -1
- package/dist/esm/models/ToolTemplateDataSource.d.ts +0 -36
- package/dist/esm/models/ToolTemplateDataSource.d.ts.map +0 -1
- package/dist/esm/models/ToolTemplateDataSource.js +0 -3
- package/dist/esm/models/ToolTemplateDataSource.js.map +0 -1
- package/dist/esm/test.d.ts +0 -78
- package/dist/esm/test.d.ts.map +0 -1
- package/dist/esm/test.js +0 -423
- package/dist/esm/test.js.map +0 -1
- package/dist/esm/utils/TemplateInterpolator.d.ts +0 -29
- package/dist/esm/utils/TemplateInterpolator.d.ts.map +0 -1
- package/dist/esm/utils/TemplateInterpolator.js +0 -206
- package/dist/esm/utils/TemplateInterpolator.js.map +0 -1
- package/dist/managers/TemplateInterpolator.d.ts +0 -19
- package/dist/managers/TemplateInterpolator.d.ts.map +0 -1
- package/dist/managers/TemplateInterpolator.js +0 -86
- package/dist/managers/TemplateInterpolator.js.map +0 -1
- package/dist/managers/ToolTipper.d.ts +0 -32
- package/dist/managers/ToolTipper.d.ts.map +0 -1
- package/dist/managers/ToolTipper.js +0 -229
- package/dist/managers/ToolTipper.js.map +0 -1
- package/dist/models/BrowserState.d.ts +0 -26
- package/dist/models/BrowserState.d.ts.map +0 -1
- package/dist/models/BrowserState.js +0 -3
- package/dist/models/BrowserState.js.map +0 -1
- package/dist/models/ResolverContext.d.ts +0 -7
- package/dist/models/ResolverContext.d.ts.map +0 -1
- package/dist/models/ResolverContext.js +0 -3
- package/dist/models/ResolverContext.js.map +0 -1
- package/dist/models/ToolTemplateDataSource.d.ts +0 -36
- package/dist/models/ToolTemplateDataSource.d.ts.map +0 -1
- package/dist/models/ToolTemplateDataSource.js +0 -3
- package/dist/models/ToolTemplateDataSource.js.map +0 -1
- package/dist/test.d.ts +0 -78
- package/dist/test.d.ts.map +0 -1
- package/dist/test.js +0 -423
- package/dist/test.js.map +0 -1
- package/dist/tsconfig.esm.tsbuildinfo +0 -1
- package/dist/utils/TemplateInterpolator.d.ts +0 -29
- package/dist/utils/TemplateInterpolator.d.ts.map +0 -1
- package/dist/utils/TemplateInterpolator.js +0 -211
- package/dist/utils/TemplateInterpolator.js.map +0 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// PlaywrightUtils.ts
|
|
3
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
4
|
};
|
|
@@ -10,6 +9,9 @@ const MiscUtils_1 = require("./MiscUtils");
|
|
|
10
9
|
const path_1 = __importDefault(require("path"));
|
|
11
10
|
const PageClosedException_1 = require("../exceptions/PageClosedException");
|
|
12
11
|
const child_process_1 = require("child_process");
|
|
12
|
+
const ControlPanel_1 = require("../managers/ControlPanel");
|
|
13
|
+
const PageInspector_1 = require("../managers/PageInspector");
|
|
14
|
+
const RunAccessibilityTestTool_1 = require("../tools/RunAccessibilityTestTool");
|
|
13
15
|
/**
|
|
14
16
|
* Miscellaneous utility functions for working with the Playwright SDK. If you are looking to
|
|
15
17
|
* instantiate a Playwright instance, see PlaywrightSetup instead.
|
|
@@ -26,7 +28,7 @@ class PlaywrightUtils {
|
|
|
26
28
|
*/
|
|
27
29
|
static async takeScreenshot(page, type = 'jpeg') {
|
|
28
30
|
try {
|
|
29
|
-
const style = `#${
|
|
31
|
+
const style = `#${ControlPanel_1.ControlPanel.DONOBU_CONTROL_PANEL_ELEMENT_ID} { display: none !important; }`;
|
|
30
32
|
return await page.screenshot({
|
|
31
33
|
style,
|
|
32
34
|
type: type,
|
|
@@ -50,56 +52,9 @@ class PlaywrightUtils {
|
|
|
50
52
|
*/
|
|
51
53
|
static async generateSelectors(element) {
|
|
52
54
|
return element.evaluate((elem) => {
|
|
53
|
-
return window.
|
|
55
|
+
return window.__donobu.generateSmartSelectors(elem);
|
|
54
56
|
});
|
|
55
57
|
}
|
|
56
|
-
/**
|
|
57
|
-
* Returns a JavaScript code snippet intended to run as an initialization
|
|
58
|
-
* script for `DonobuFlow` flows. This script helps other scripts to know
|
|
59
|
-
* which elements have mouse-click related event listeners attached to them
|
|
60
|
-
* via inspecting the {@code window.donobuGetClickableElements} element array.
|
|
61
|
-
*/
|
|
62
|
-
static clickableElementsTrackerInitScript() {
|
|
63
|
-
return PlaywrightUtils.CLICKABLE_ELEMENTS_TRACKER_INIT_SCRIPT;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Returns a JavaScript code snippet intended to run as an initialization
|
|
67
|
-
* script for `DonobuFlow` flows. This script helps flows handle/track
|
|
68
|
-
* prompts/confirmations. This is done specially since these browser actions
|
|
69
|
-
* pause the Javascript main thread and also cause issues with running various
|
|
70
|
-
* Playwright actions. See `DonobuFlow.onDialog(Dialog)` for details.
|
|
71
|
-
*/
|
|
72
|
-
static dialogPromptTrackerInitScript() {
|
|
73
|
-
return PlaywrightUtils.DIALOG_PROMPT_TRACKER_INIT_SCRIPT;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Returns a JavaScript code snippet intended to run as an initialization
|
|
77
|
-
* script for `DonobuFlow` flows. This is an in-page version of the
|
|
78
|
-
* `SelectorGenerator` class so that smart selectors can be generated in
|
|
79
|
-
* the web client. This is done so the
|
|
80
|
-
* {@link PlaywrightUtils.pageInteractionsTrackerInitScript()} can generate
|
|
81
|
-
* smart selectors on the fly and pass them to the `PageInteractionTracker`
|
|
82
|
-
* so the page interactions can be converted to synthetic `ToolCallResult`s.
|
|
83
|
-
*/
|
|
84
|
-
static smartSelectorGeneratorInitScript() {
|
|
85
|
-
return PlaywrightUtils.SMART_SELECTOR_GENERATOR_INIT_SCRIPT;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* This script is used by the `RunAccessibilityTestTool` to test the
|
|
89
|
-
* accessibility of a webpage. This script is injected on page load, rather
|
|
90
|
-
* than when running the `RunAccessibilityTestTool` because we need to bypass
|
|
91
|
-
* webpage Content Security Policy (CSP); this is done by injecting the
|
|
92
|
-
* script via `BrowserContext#addInitScript`.
|
|
93
|
-
*/
|
|
94
|
-
static accessibilityTestInitScript() {
|
|
95
|
-
return PlaywrightUtils.ACCESSIBILITY_TEST_INIT_SCRIPT;
|
|
96
|
-
}
|
|
97
|
-
static pageInteractionsTrackerInitScript() {
|
|
98
|
-
return PlaywrightUtils.PAGE_INTERACTIONS_TRACKER_INIT_SCRIPT;
|
|
99
|
-
}
|
|
100
|
-
static donobuControlPanelInitScript() {
|
|
101
|
-
return PlaywrightUtils.DONOBU_CONTROL_PANEL_INIT_SCRIPT;
|
|
102
|
-
}
|
|
103
58
|
/**
|
|
104
59
|
* Sets up the given browser context so that it can be minimally used by the
|
|
105
60
|
* rest of Donobu. This involves registering virtual routes for serving up
|
|
@@ -107,614 +62,10 @@ class PlaywrightUtils {
|
|
|
107
62
|
* scripts.
|
|
108
63
|
*/
|
|
109
64
|
static async setupBasicBrowserContext(browserContext) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
await browserContext.
|
|
113
|
-
|
|
114
|
-
status: 200,
|
|
115
|
-
contentType: 'image/svg+xml',
|
|
116
|
-
body: MiscUtils_1.MiscUtils.getResourceFileAsString('donobu-virtual-mouse.svg'),
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
await browserContext.route('**/donobu.css', async (route) => {
|
|
120
|
-
await route.fulfill({
|
|
121
|
-
status: 200,
|
|
122
|
-
contentType: 'text/css',
|
|
123
|
-
body: MiscUtils_1.MiscUtils.getResourceFileAsString('donobu.css'),
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
await browserContext.addInitScript(PlaywrightUtils.accessibilityTestInitScript());
|
|
127
|
-
await browserContext.addInitScript(PlaywrightUtils.clickableElementsTrackerInitScript());
|
|
128
|
-
await browserContext.addInitScript(PlaywrightUtils.dialogPromptTrackerInitScript());
|
|
129
|
-
await browserContext.addInitScript(PlaywrightUtils.smartSelectorGeneratorInitScript());
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Returns the given locator or its label, if it has one and is associated with a labelable element.
|
|
133
|
-
* Labelable elements include: button, input (except hidden), meter, output, progress, select, and textarea.
|
|
134
|
-
*/
|
|
135
|
-
static async getLocatorOrItsLabel(element) {
|
|
136
|
-
const timeoutMilliseconds = 5000;
|
|
137
|
-
try {
|
|
138
|
-
// First check if the element itself is visible
|
|
139
|
-
try {
|
|
140
|
-
await element.waitFor({
|
|
141
|
-
state: 'visible',
|
|
142
|
-
timeout: timeoutMilliseconds,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
// If the element itself isn't visible, just return it without looking for labels
|
|
147
|
-
if (PlaywrightUtils.isPageClosedError(error)) {
|
|
148
|
-
throw new PageClosedException_1.PageClosedException();
|
|
149
|
-
}
|
|
150
|
-
return element;
|
|
151
|
-
}
|
|
152
|
-
// Check if the element is a labelable element
|
|
153
|
-
// See https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/label
|
|
154
|
-
const isLabelable = await element.evaluate((elem) => {
|
|
155
|
-
const tagName = elem.tagName.toLowerCase();
|
|
156
|
-
if (tagName === 'button' ||
|
|
157
|
-
tagName === 'meter' ||
|
|
158
|
-
tagName === 'output' ||
|
|
159
|
-
tagName === 'progress' ||
|
|
160
|
-
tagName === 'select' ||
|
|
161
|
-
tagName === 'textarea') {
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
if (tagName === 'input') {
|
|
165
|
-
// Input is labelable except when type="hidden"
|
|
166
|
-
const type = elem.getAttribute('type')?.toLowerCase() || 'text';
|
|
167
|
-
return type !== 'hidden';
|
|
168
|
-
}
|
|
169
|
-
return false;
|
|
170
|
-
});
|
|
171
|
-
// If the element is not labelable, return the original locator
|
|
172
|
-
if (!isLabelable) {
|
|
173
|
-
return element;
|
|
174
|
-
}
|
|
175
|
-
// Try to get the ID attribute
|
|
176
|
-
const id = await element
|
|
177
|
-
.getAttribute('id', {
|
|
178
|
-
timeout: timeoutMilliseconds,
|
|
179
|
-
})
|
|
180
|
-
.catch(() => null);
|
|
181
|
-
if (id) {
|
|
182
|
-
try {
|
|
183
|
-
// Get the underlying ElementHandle first
|
|
184
|
-
const handle = await element.elementHandle({
|
|
185
|
-
timeout: timeoutMilliseconds,
|
|
186
|
-
});
|
|
187
|
-
if (handle) {
|
|
188
|
-
// From the handle, retrieve the frame that owns this element
|
|
189
|
-
const frame = await handle.ownerFrame();
|
|
190
|
-
if (frame) {
|
|
191
|
-
// Look for label with matching 'for' attribute
|
|
192
|
-
const labelByFor = frame.locator(`label[for="${id}"]`);
|
|
193
|
-
// Check if the label exists and is visible
|
|
194
|
-
if ((await labelByFor.count()) > 0) {
|
|
195
|
-
try {
|
|
196
|
-
await labelByFor.waitFor({
|
|
197
|
-
state: 'visible',
|
|
198
|
-
timeout: timeoutMilliseconds,
|
|
199
|
-
});
|
|
200
|
-
return labelByFor;
|
|
201
|
-
}
|
|
202
|
-
catch (visibilityError) {
|
|
203
|
-
// If label exists but isn't visible, continue to other checks
|
|
204
|
-
if (PlaywrightUtils.isPageClosedError(visibilityError)) {
|
|
205
|
-
throw new PageClosedException_1.PageClosedException();
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
catch (error) {
|
|
213
|
-
// Handle errors in the ElementHandle/frame retrieval, but continue to other checks
|
|
214
|
-
if (PlaywrightUtils.isPageClosedError(error)) {
|
|
215
|
-
throw new PageClosedException_1.PageClosedException();
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
// Fallback: look for a directly wrapping label (immediate ancestor)
|
|
220
|
-
// Using 'xpath=parent::label' to only get direct parent if it's a label
|
|
221
|
-
const directParentLabel = element.locator('xpath=parent::label');
|
|
222
|
-
if ((await directParentLabel.count()) > 0) {
|
|
223
|
-
try {
|
|
224
|
-
await directParentLabel.waitFor({
|
|
225
|
-
state: 'visible',
|
|
226
|
-
timeout: timeoutMilliseconds,
|
|
227
|
-
});
|
|
228
|
-
return directParentLabel;
|
|
229
|
-
}
|
|
230
|
-
catch (visibilityError) {
|
|
231
|
-
if (PlaywrightUtils.isPageClosedError(visibilityError)) {
|
|
232
|
-
throw new PageClosedException_1.PageClosedException();
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
// If direct parent isn't a label, check any ancestor label
|
|
237
|
-
const wrappingLabel = element.locator('xpath=ancestor::label[1]'); // Get closest ancestor label
|
|
238
|
-
if ((await wrappingLabel.count()) > 0) {
|
|
239
|
-
try {
|
|
240
|
-
await wrappingLabel.waitFor({
|
|
241
|
-
state: 'visible',
|
|
242
|
-
timeout: timeoutMilliseconds,
|
|
243
|
-
});
|
|
244
|
-
return wrappingLabel;
|
|
245
|
-
}
|
|
246
|
-
catch (visibilityError) {
|
|
247
|
-
if (PlaywrightUtils.isPageClosedError(visibilityError)) {
|
|
248
|
-
throw new PageClosedException_1.PageClosedException();
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
// Neither "for" nor wrapping label found or visible
|
|
253
|
-
return element;
|
|
254
|
-
}
|
|
255
|
-
catch (error) {
|
|
256
|
-
if (PlaywrightUtils.isPageClosedError(error)) {
|
|
257
|
-
throw new PageClosedException_1.PageClosedException();
|
|
258
|
-
}
|
|
259
|
-
throw error;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Reads and clears the desired next state as directed by the in-flow control
|
|
264
|
-
* panel. Note that the control panel does not have carte blanche control, as
|
|
265
|
-
* we only support the control panel signaling an intent to pause and resume a
|
|
266
|
-
* flow.
|
|
267
|
-
*/
|
|
268
|
-
static async popControlPanelNextDesiredState(page) {
|
|
269
|
-
try {
|
|
270
|
-
const desiredNextState = await page.evaluate(() => {
|
|
271
|
-
const tmpState = window.donobuNextState;
|
|
272
|
-
window.donobuNextState = null;
|
|
273
|
-
return tmpState;
|
|
274
|
-
});
|
|
275
|
-
if (desiredNextState) {
|
|
276
|
-
return desiredNextState;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
catch (error) {
|
|
280
|
-
if (PlaywrightUtils.isPageClosedError(error)) {
|
|
281
|
-
throw new PageClosedException_1.PageClosedException();
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
throw error;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return null;
|
|
288
|
-
}
|
|
289
|
-
/**
|
|
290
|
-
* Hides the control panel by setting its display to 'none'. This ensures the
|
|
291
|
-
* panel is not visible and does not intercept mouse events.
|
|
292
|
-
*/
|
|
293
|
-
static async hideControlPanel(focusedPage, flowMetadata) {
|
|
294
|
-
if (!flowMetadata.isControlPanelEnabled) {
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
for (const page of focusedPage.context().pages()) {
|
|
298
|
-
try {
|
|
299
|
-
await page.evaluate((panelId) => {
|
|
300
|
-
const element = document.querySelector(`#${panelId}`);
|
|
301
|
-
if (element) {
|
|
302
|
-
element.style.display = 'none';
|
|
303
|
-
}
|
|
304
|
-
}, PlaywrightUtils.DONOBU_CONTROL_PANEL_ELEMENT_ID);
|
|
305
|
-
}
|
|
306
|
-
catch (error) {
|
|
307
|
-
if (PlaywrightUtils.isPageClosedError(error)) {
|
|
308
|
-
// Pass; this can happen normally.
|
|
309
|
-
}
|
|
310
|
-
else {
|
|
311
|
-
Logger_1.appLogger.error(`Unexpected exception while attempting to show control panel for flow '${flowMetadata.id}'`, error);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Shows the control panel by resetting its display property. Assumes the
|
|
318
|
-
* original display was 'block'.
|
|
319
|
-
*/
|
|
320
|
-
static async showControlPanel(focusedPage, flowMetadata) {
|
|
321
|
-
if (!flowMetadata.isControlPanelEnabled) {
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
for (const page of focusedPage.context().pages()) {
|
|
325
|
-
try {
|
|
326
|
-
await page.evaluate((panelId) => {
|
|
327
|
-
const element = document.querySelector(`#${panelId}`);
|
|
328
|
-
if (element) {
|
|
329
|
-
element.style.display = 'block';
|
|
330
|
-
}
|
|
331
|
-
}, PlaywrightUtils.DONOBU_CONTROL_PANEL_ELEMENT_ID);
|
|
332
|
-
}
|
|
333
|
-
catch (error) {
|
|
334
|
-
if (PlaywrightUtils.isPageClosedError(error)) {
|
|
335
|
-
// Pass; this can happen normally.
|
|
336
|
-
}
|
|
337
|
-
else {
|
|
338
|
-
Logger_1.appLogger.error(`Unexpected exception while attempting to show control panel for flow '${flowMetadata.id}'`, error);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
/**
|
|
344
|
-
* Updates all control panels in the given browser context. If the provided
|
|
345
|
-
* headline is non-null, the headline in the control panel will be updated.
|
|
346
|
-
*/
|
|
347
|
-
static async updateControlPanel(focusedPage, flowMetadata, headline) {
|
|
348
|
-
const controlPanelQuerySelector = `#${PlaywrightUtils.DONOBU_CONTROL_PANEL_ELEMENT_ID}`;
|
|
349
|
-
const controlPanelHeadlineQuerySelector = `#${PlaywrightUtils.DONOBU_CONTROL_PANEL_HEADLINE_ELEMENT_ID}`;
|
|
350
|
-
for (const page of focusedPage.context().pages()) {
|
|
351
|
-
try {
|
|
352
|
-
const controlPanel = page.locator(controlPanelQuerySelector).first();
|
|
353
|
-
if ((await controlPanel.count()) === 1) {
|
|
354
|
-
await controlPanel.evaluate((element, state) => {
|
|
355
|
-
element.donobuFlowState = state;
|
|
356
|
-
}, flowMetadata.state.toString());
|
|
357
|
-
if (headline) {
|
|
358
|
-
const controlPanelHeadline = page
|
|
359
|
-
.locator(controlPanelHeadlineQuerySelector)
|
|
360
|
-
.first();
|
|
361
|
-
if ((await controlPanelHeadline.count()) === 1) {
|
|
362
|
-
const escapedMessage = headline.replace(/[\\"']/g, '\\$&');
|
|
363
|
-
await controlPanelHeadline.evaluate((element, message) => {
|
|
364
|
-
element.textContent = message;
|
|
365
|
-
}, escapedMessage);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
catch (error) {
|
|
371
|
-
if (PlaywrightUtils.isPageClosedError(error)) {
|
|
372
|
-
// Pass; this can happen normally.
|
|
373
|
-
}
|
|
374
|
-
else {
|
|
375
|
-
Logger_1.appLogger.error(`Unexpected exception while attempting to show control panel for flow '${flowMetadata.id}'`, error);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* Returns all elements that have the
|
|
382
|
-
* {@link PlaywrightUtils.DONOBU_INTERACTABLE_ATTRIBUTE} HTML attribute.
|
|
383
|
-
**/
|
|
384
|
-
static async getAttributedInteractableElements(page) {
|
|
385
|
-
try {
|
|
386
|
-
const interactableElements = [];
|
|
387
|
-
const frames = page
|
|
388
|
-
.frames()
|
|
389
|
-
.filter((frame) => PlaywrightUtils.frameFilter(frame));
|
|
390
|
-
for (const frame of frames) {
|
|
391
|
-
const attributeMap = await frame.evaluate((interactableAttribute) => {
|
|
392
|
-
const getOuterHTMLWithoutChildrenExceptForSelectElements = (element) => {
|
|
393
|
-
if (element.tagName.toLowerCase() === 'select') {
|
|
394
|
-
return element.outerHTML;
|
|
395
|
-
}
|
|
396
|
-
else {
|
|
397
|
-
const tempElement = document.createElement('div');
|
|
398
|
-
tempElement.appendChild(element.cloneNode(false));
|
|
399
|
-
return tempElement.innerHTML;
|
|
400
|
-
}
|
|
401
|
-
};
|
|
402
|
-
const attributeToHtmlMap = {};
|
|
403
|
-
const elements = document.querySelectorAll(`[${interactableAttribute}]`);
|
|
404
|
-
elements.forEach((element) => {
|
|
405
|
-
const attributeValue = element.getAttribute(interactableAttribute);
|
|
406
|
-
const outerHtml = getOuterHTMLWithoutChildrenExceptForSelectElements(element);
|
|
407
|
-
if (attributeValue) {
|
|
408
|
-
attributeToHtmlMap[attributeValue] = outerHtml;
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
return attributeToHtmlMap;
|
|
412
|
-
}, PlaywrightUtils.DONOBU_INTERACTABLE_ATTRIBUTE);
|
|
413
|
-
Object.entries(attributeMap).forEach(([donobuAttributeValue, htmlSnippet]) => {
|
|
414
|
-
interactableElements.push({
|
|
415
|
-
donobuAttributeValue,
|
|
416
|
-
htmlSnippet,
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
return interactableElements;
|
|
421
|
-
}
|
|
422
|
-
catch (error) {
|
|
423
|
-
if (PlaywrightUtils.isPageClosedError(error)) {
|
|
424
|
-
throw new PageClosedException_1.PageClosedException();
|
|
425
|
-
}
|
|
426
|
-
else {
|
|
427
|
-
throw error;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Assigns a globally unique {@link PlaywrightUtils.DONOBU_INTERACTABLE_ATTRIBUTE}
|
|
433
|
-
* attribute value to each visible, interactable, element in the given page.
|
|
434
|
-
* Any pre-existing {@link PlaywrightUtils.DONOBU_INTERACTABLE_ATTRIBUTE}
|
|
435
|
-
* attributes will be removed.
|
|
436
|
-
*/
|
|
437
|
-
static async attributeInteractableElements(page) {
|
|
438
|
-
try {
|
|
439
|
-
// Remove any preexisting attributes
|
|
440
|
-
await this.deattributeVisibleInteractableElements(page);
|
|
441
|
-
// Get viewport dimensions and scroll position properly
|
|
442
|
-
const viewportInfo = await page.evaluate(() => {
|
|
443
|
-
return {
|
|
444
|
-
viewportWidth: window.innerWidth,
|
|
445
|
-
viewportHeight: window.innerHeight,
|
|
446
|
-
scrollX: window.scrollX || window.pageXOffset,
|
|
447
|
-
scrollY: window.scrollY || window.pageYOffset,
|
|
448
|
-
};
|
|
449
|
-
});
|
|
450
|
-
// 1) Attribute elements in the main page
|
|
451
|
-
let annotationOffset = await page.evaluate(this.attributeElementsInContext, [0, PlaywrightUtils.DONOBU_INTERACTABLE_ATTRIBUTE]);
|
|
452
|
-
// 2) Check child frames, attributing elements if the frame is (partially) in view
|
|
453
|
-
const frames = page
|
|
454
|
-
.frames()
|
|
455
|
-
.filter((frame) => PlaywrightUtils.frameFilter(frame) && frame !== page.mainFrame());
|
|
456
|
-
for (const frame of frames) {
|
|
457
|
-
const elementHandle = await frame.frameElement();
|
|
458
|
-
if (!elementHandle) {
|
|
459
|
-
continue;
|
|
460
|
-
}
|
|
461
|
-
const boundingBox = await elementHandle.boundingBox();
|
|
462
|
-
if (!boundingBox) {
|
|
463
|
-
continue;
|
|
464
|
-
}
|
|
465
|
-
// boundingBox coordinates are already in viewport space, so we need to account for scroll
|
|
466
|
-
const isInViewport = boundingBox.x + boundingBox.width > 0 &&
|
|
467
|
-
boundingBox.x < viewportInfo.viewportWidth &&
|
|
468
|
-
boundingBox.y + boundingBox.height > 0 &&
|
|
469
|
-
boundingBox.y < viewportInfo.viewportHeight;
|
|
470
|
-
if (isInViewport) {
|
|
471
|
-
annotationOffset = await frame.evaluate(this.attributeElementsInContext, [
|
|
472
|
-
annotationOffset,
|
|
473
|
-
PlaywrightUtils.DONOBU_INTERACTABLE_ATTRIBUTE,
|
|
474
|
-
]);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
catch (error) {
|
|
479
|
-
if (PlaywrightUtils.isPageClosedError(error)) {
|
|
480
|
-
throw new PageClosedException_1.PageClosedException();
|
|
481
|
-
}
|
|
482
|
-
else {
|
|
483
|
-
throw error;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
/**
|
|
488
|
-
* Annotate all elements in the given page that have a
|
|
489
|
-
* {@link PlaywrightUtils.DONOBU_INTERACTABLE_ATTRIBUTE} HTML attribute.
|
|
490
|
-
*
|
|
491
|
-
* The annotations are placed in a shadow root to avoid site-specific CSS,
|
|
492
|
-
* each having a {@link PlaywrightUtils.DONOBU_ANNOTATION_ATTRIBUTE} attribute.
|
|
493
|
-
*/
|
|
494
|
-
static async annotateInteractableElements(page) {
|
|
495
|
-
try {
|
|
496
|
-
// Filter frames as needed
|
|
497
|
-
const frames = page
|
|
498
|
-
.frames()
|
|
499
|
-
.filter((frame) => PlaywrightUtils.frameFilter(frame));
|
|
500
|
-
for (const frame of frames) {
|
|
501
|
-
await frame.evaluate(([interactableAttr, annotationAttr]) => {
|
|
502
|
-
// 1) Ensure we have a shadow container in the main document
|
|
503
|
-
let container = document.getElementById('annotation-shadow-container');
|
|
504
|
-
if (!container) {
|
|
505
|
-
container = document.createElement('div');
|
|
506
|
-
container.id = 'annotation-shadow-container';
|
|
507
|
-
// Position container so child elements can be absolutely placed
|
|
508
|
-
Object.assign(container.style, {
|
|
509
|
-
position: 'fixed', // anchor to viewport
|
|
510
|
-
inset: '0', // stretch across it
|
|
511
|
-
pointerEvents: 'none', // Let clicks pass through
|
|
512
|
-
zIndex: '2147483647', // win every z-index fight
|
|
513
|
-
});
|
|
514
|
-
// Check if document.body exists before trying to append.
|
|
515
|
-
if (document.body) {
|
|
516
|
-
document.body.appendChild(container);
|
|
517
|
-
}
|
|
518
|
-
else if (document.documentElement) {
|
|
519
|
-
// Fall back to document.documentElement if body does not exist.
|
|
520
|
-
document.documentElement.appendChild(container);
|
|
521
|
-
}
|
|
522
|
-
else {
|
|
523
|
-
// If neither exists, we can't proceed with annotations in this frame.
|
|
524
|
-
console.warn(`Cannot create annotation container for ${window.location.href} since the document structure is incomplete`);
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
// Attach a shadow root
|
|
528
|
-
const shadowRoot = container.attachShadow({ mode: 'open' });
|
|
529
|
-
// Add a <style> element inside the shadow root to reset and define annotation styles
|
|
530
|
-
const style = document.createElement('style');
|
|
531
|
-
style.textContent = `
|
|
532
|
-
:host {
|
|
533
|
-
all: initial; /* Reset styles in shadow root */
|
|
534
|
-
}
|
|
535
|
-
.annotation {
|
|
536
|
-
position: absolute;
|
|
537
|
-
z-index: 2147483647;
|
|
538
|
-
background-color: black;
|
|
539
|
-
color: white;
|
|
540
|
-
width: 40px;
|
|
541
|
-
height: 40px;
|
|
542
|
-
border-radius: 50%;
|
|
543
|
-
display: flex;
|
|
544
|
-
align-items: center;
|
|
545
|
-
justify-content: center;
|
|
546
|
-
font-size: 14px;
|
|
547
|
-
font-weight: bold;
|
|
548
|
-
line-height: 20px;
|
|
549
|
-
text-align: center;
|
|
550
|
-
box-shadow: 0px 2px 4px rgba(0,0,0,0.2);
|
|
551
|
-
border: 4px solid #FF4136;
|
|
552
|
-
pointer-events: none;
|
|
553
|
-
}
|
|
554
|
-
`;
|
|
555
|
-
shadowRoot.appendChild(style);
|
|
556
|
-
}
|
|
557
|
-
// Retrieve the shadow root to place annotation elements
|
|
558
|
-
const containerEl = document.getElementById('annotation-shadow-container');
|
|
559
|
-
if (!containerEl?.shadowRoot) {
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
const shadowRoot = containerEl.shadowRoot;
|
|
563
|
-
// 2) Factory to create a new annotation inside the shadow root
|
|
564
|
-
const createAnnotation = (value) => {
|
|
565
|
-
const annotation = document.createElement('div');
|
|
566
|
-
annotation.classList.add('annotation');
|
|
567
|
-
annotation.dataset[annotationAttr] = '1';
|
|
568
|
-
annotation.textContent = value;
|
|
569
|
-
return annotation;
|
|
570
|
-
};
|
|
571
|
-
// 3) Position annotation relative to an element
|
|
572
|
-
const positionAnnotation = (annotation, element) => {
|
|
573
|
-
const rect = element.getBoundingClientRect();
|
|
574
|
-
// Center the annotation on the element, adjusting for its size
|
|
575
|
-
const x = rect.left + rect.width / 2 - 20 + window.scrollX;
|
|
576
|
-
const y = rect.top + rect.height / 2 - 20 + window.scrollY;
|
|
577
|
-
annotation.style.left = `${x}px`;
|
|
578
|
-
annotation.style.top = `${y}px`;
|
|
579
|
-
};
|
|
580
|
-
// 4) Traverse DOM (including any nested shadow roots) to find interactable elements
|
|
581
|
-
const processNode = (root) => {
|
|
582
|
-
// Find elements with the interactable attribute
|
|
583
|
-
const elements = root.querySelectorAll(`[${interactableAttr}]`);
|
|
584
|
-
elements.forEach((element) => {
|
|
585
|
-
const value = element.getAttribute(interactableAttr);
|
|
586
|
-
if (value) {
|
|
587
|
-
const annotation = createAnnotation(value);
|
|
588
|
-
shadowRoot.appendChild(annotation);
|
|
589
|
-
positionAnnotation(annotation, element);
|
|
590
|
-
}
|
|
591
|
-
});
|
|
592
|
-
// Recursively process any child shadow roots
|
|
593
|
-
root.querySelectorAll('*').forEach((el) => {
|
|
594
|
-
if (el.shadowRoot) {
|
|
595
|
-
processNode(el.shadowRoot);
|
|
596
|
-
}
|
|
597
|
-
});
|
|
598
|
-
};
|
|
599
|
-
// Start processing from the (frame) document root
|
|
600
|
-
processNode(document);
|
|
601
|
-
}, [
|
|
602
|
-
PlaywrightUtils.DONOBU_INTERACTABLE_ATTRIBUTE,
|
|
603
|
-
PlaywrightUtils.convertToJsAttribute(PlaywrightUtils.DONOBU_ANNOTATION_ATTRIBUTE),
|
|
604
|
-
]);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
catch (error) {
|
|
608
|
-
if (PlaywrightUtils.isPageClosedError(error)) {
|
|
609
|
-
throw new PageClosedException_1.PageClosedException();
|
|
610
|
-
}
|
|
611
|
-
else {
|
|
612
|
-
throw error;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
/**
|
|
617
|
-
* Removes all annotations with a {@link PlaywrightUtils.DONOBU_ANNOTATION_ATTRIBUTE}
|
|
618
|
-
* HTML attribute in the given page, including any containers in shadow DOM.
|
|
619
|
-
*/
|
|
620
|
-
static async removeDonobuAnnotations(page) {
|
|
621
|
-
try {
|
|
622
|
-
const frames = page
|
|
623
|
-
.frames()
|
|
624
|
-
.filter((frame) => PlaywrightUtils.frameFilter(frame));
|
|
625
|
-
for (const frame of frames) {
|
|
626
|
-
await frame.evaluate(() => {
|
|
627
|
-
const containerId = 'annotation-shadow-container';
|
|
628
|
-
const container = document.getElementById(containerId);
|
|
629
|
-
if (container) {
|
|
630
|
-
container.remove();
|
|
631
|
-
}
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
catch (error) {
|
|
636
|
-
if (PlaywrightUtils.isPageClosedError(error)) {
|
|
637
|
-
throw new PageClosedException_1.PageClosedException();
|
|
638
|
-
}
|
|
639
|
-
throw error;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
/**
|
|
643
|
-
* Removes the {@link PlaywrightUtils.DONOBU_INTERACTABLE_ATTRIBUTE} HTML
|
|
644
|
-
* attribute for all elements in the given page. This attribute is normally
|
|
645
|
-
* added by the {@link PlaywrightUtils.attributeInteractableElements(Page)}
|
|
646
|
-
* function.
|
|
647
|
-
*/
|
|
648
|
-
static async deattributeVisibleInteractableElements(page) {
|
|
649
|
-
try {
|
|
650
|
-
const frames = page
|
|
651
|
-
.frames()
|
|
652
|
-
.filter((frame) => PlaywrightUtils.frameFilter(frame));
|
|
653
|
-
for (const frame of frames) {
|
|
654
|
-
await frame.evaluate((attr) => {
|
|
655
|
-
document.querySelectorAll(`[${attr}]`).forEach((element) => {
|
|
656
|
-
element.removeAttribute(attr);
|
|
657
|
-
});
|
|
658
|
-
}, PlaywrightUtils.DONOBU_INTERACTABLE_ATTRIBUTE);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
catch (error) {
|
|
662
|
-
if (PlaywrightUtils.isPageClosedError(error)) {
|
|
663
|
-
throw new PageClosedException_1.PageClosedException();
|
|
664
|
-
}
|
|
665
|
-
else {
|
|
666
|
-
throw error;
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
static async parseUnambiguousSelector(elementHandle) {
|
|
671
|
-
try {
|
|
672
|
-
return await elementHandle.evaluate((element) => {
|
|
673
|
-
const escapeCssIdentifier = (ident) => {
|
|
674
|
-
return ident.replace(/([!"#$%&'()*+,./:;<=>?@[\\]^{|}~])/g, '\\$1');
|
|
675
|
-
};
|
|
676
|
-
const getPath = (el) => {
|
|
677
|
-
if (el.id)
|
|
678
|
-
return '#' + escapeCssIdentifier(el.id);
|
|
679
|
-
if (!el.parentNode || el.parentNode.nodeType === Node.DOCUMENT_NODE)
|
|
680
|
-
return '';
|
|
681
|
-
const siblings = Array.from(el.parentNode.children).filter((e) => e.tagName === el.tagName);
|
|
682
|
-
const index = siblings.indexOf(el) + 1;
|
|
683
|
-
const tag = el.tagName.toLowerCase();
|
|
684
|
-
const parentPath = getPath(el.parentNode);
|
|
685
|
-
return (parentPath +
|
|
686
|
-
(parentPath ? ' > ' : '') +
|
|
687
|
-
tag +
|
|
688
|
-
(siblings.length > 1 ? ':nth-child(' + index + ')' : ''));
|
|
689
|
-
};
|
|
690
|
-
return getPath(element);
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
catch (error) {
|
|
694
|
-
if (PlaywrightUtils.isPageClosedError(error)) {
|
|
695
|
-
throw new PageClosedException_1.PageClosedException();
|
|
696
|
-
}
|
|
697
|
-
else {
|
|
698
|
-
throw error;
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
|
-
* Converts an HTML attribute to a JavaScript attribute. For example,
|
|
704
|
-
* "data-foo-bar" is turned into "fooBar". Notice the dropping of the "data-"
|
|
705
|
-
* prefix, and the conversion from kebab-case to camelCase.
|
|
706
|
-
*/
|
|
707
|
-
static convertToJsAttribute(htmlAttribute) {
|
|
708
|
-
if (htmlAttribute.startsWith('data-')) {
|
|
709
|
-
htmlAttribute = htmlAttribute.substring(5);
|
|
710
|
-
}
|
|
711
|
-
const parts = htmlAttribute.split('-');
|
|
712
|
-
const jsAttribute = parts[0] +
|
|
713
|
-
parts
|
|
714
|
-
.slice(1)
|
|
715
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
716
|
-
.join('');
|
|
717
|
-
return jsAttribute;
|
|
65
|
+
await browserContext.addInitScript(RunAccessibilityTestTool_1.RunAccessibilityTestTool.INIT_SCRIPT);
|
|
66
|
+
await browserContext.addInitScript(PageInspector_1.PageInspector.INIT_SCRIPT);
|
|
67
|
+
await browserContext.addInitScript(PlaywrightUtils.DIALOG_PROMPT_TRACKER_INIT_SCRIPT);
|
|
68
|
+
await browserContext.addInitScript(PlaywrightUtils.SMART_SELECTOR_GENERATOR_INIT_SCRIPT);
|
|
718
69
|
}
|
|
719
70
|
/**
|
|
720
71
|
* Returned true IFF the given error is a Playwright error regarding page closing,
|
|
@@ -805,295 +156,142 @@ class PlaywrightUtils {
|
|
|
805
156
|
}
|
|
806
157
|
}
|
|
807
158
|
}
|
|
808
|
-
static frameFilter(frame) {
|
|
809
|
-
return (!frame.isDetached() &&
|
|
810
|
-
!frame.url().startsWith('about:') &&
|
|
811
|
-
!frame.url().startsWith('chrome:') &&
|
|
812
|
-
!frame.url().startsWith('edge:'));
|
|
813
|
-
}
|
|
814
159
|
/**
|
|
815
|
-
*
|
|
816
|
-
*
|
|
817
|
-
*
|
|
160
|
+
* Takes a screenshot of the webpage as if it were scrolled down by one viewport.
|
|
161
|
+
* If the page cannot be scrolled as a whole, tries to find and scroll individual
|
|
162
|
+
* scrollable elements on the page. After taking the screenshot, it reverts to
|
|
163
|
+
* the original scroll position.
|
|
164
|
+
*
|
|
165
|
+
* @param page The Playwright Page to take a screenshot of
|
|
166
|
+
* @returns An image buffer of the scrolled down view, or undefined if scrolling isn't possible
|
|
818
167
|
*/
|
|
819
|
-
static
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
function isElementMoreThanHalfInViewport(rect) {
|
|
830
|
-
const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
|
|
831
|
-
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
|
|
832
|
-
const visibleWidth = Math.min(rect.right, viewportWidth) - Math.max(rect.left, 0);
|
|
833
|
-
const visibleHeight = Math.min(rect.bottom, viewportHeight) - Math.max(rect.top, 0);
|
|
834
|
-
const visibleArea = Math.max(0, visibleWidth) * Math.max(0, visibleHeight);
|
|
835
|
-
const elementArea = rect.width * rect.height;
|
|
836
|
-
return visibleArea >= elementArea / 2;
|
|
837
|
-
}
|
|
838
|
-
function isElementEnabled(element, style) {
|
|
839
|
-
// Check standard disabled attribute (for form controls like button, input, etc.)
|
|
840
|
-
if (element.hasAttribute('disabled')) {
|
|
841
|
-
return false;
|
|
842
|
-
}
|
|
843
|
-
// Check for ARIA attributes that indicate non-interactivity
|
|
844
|
-
if (element.getAttribute('aria-disabled') === 'true' ||
|
|
845
|
-
element.getAttribute('aria-hidden') === 'true') {
|
|
846
|
-
return false;
|
|
847
|
-
}
|
|
848
|
-
// Check for pointer-events: none which prevents interactions
|
|
849
|
-
if (style.pointerEvents === 'none') {
|
|
850
|
-
return false;
|
|
851
|
-
}
|
|
852
|
-
// Check for ARIA attributes indicating disabled state
|
|
853
|
-
if (element.getAttribute('aria-disabled') === 'true') {
|
|
854
|
-
return false;
|
|
855
|
-
}
|
|
856
|
-
// Check for inert attribute which makes elements non-interactive
|
|
857
|
-
if (element.hasAttribute('inert')) {
|
|
858
|
-
return false;
|
|
859
|
-
}
|
|
860
|
-
// If the element is in a form and the fieldset is disabled, it might be disabled as well
|
|
861
|
-
let parent = element.parentElement;
|
|
862
|
-
while (parent) {
|
|
863
|
-
if (parent.tagName.toLowerCase() === 'fieldset' &&
|
|
864
|
-
parent.hasAttribute('disabled') &&
|
|
865
|
-
element.tagName.toLowerCase() !== 'legend') {
|
|
866
|
-
return false;
|
|
867
|
-
}
|
|
868
|
-
parent = parent.parentElement;
|
|
869
|
-
}
|
|
870
|
-
return true;
|
|
871
|
-
}
|
|
872
|
-
/**
|
|
873
|
-
* Generate a few test points on the element's bounding box. We only need
|
|
874
|
-
* a small offset (1px) from each corner, plus the center.
|
|
875
|
-
*/
|
|
876
|
-
function getPointsToCheck(rect) {
|
|
877
|
-
const cornerOffset = 1;
|
|
878
|
-
return [
|
|
879
|
-
{ x: rect.left + cornerOffset, y: rect.top + cornerOffset },
|
|
880
|
-
{ x: rect.right - cornerOffset, y: rect.top + cornerOffset },
|
|
881
|
-
{ x: rect.left + cornerOffset, y: rect.bottom - cornerOffset },
|
|
882
|
-
{ x: rect.right - cornerOffset, y: rect.bottom - cornerOffset },
|
|
883
|
-
{ x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 },
|
|
884
|
-
];
|
|
885
|
-
}
|
|
886
|
-
/**
|
|
887
|
-
* Like `elementFromPoint` but continues walking shadow roots if found.
|
|
888
|
-
*/
|
|
889
|
-
function getDeepElementFromPoint(x, y) {
|
|
890
|
-
let el = document.elementFromPoint(x, y);
|
|
891
|
-
while (el && el.shadowRoot) {
|
|
892
|
-
const shadowEl = el.shadowRoot.elementFromPoint(x, y);
|
|
893
|
-
if (!shadowEl || shadowEl === el)
|
|
894
|
-
break;
|
|
895
|
-
el = shadowEl;
|
|
896
|
-
}
|
|
897
|
-
return el;
|
|
898
|
-
}
|
|
899
|
-
function getAllInteractableElements(root) {
|
|
900
|
-
const selector = [
|
|
901
|
-
// ----- STANDARD HTML INTERACTIVE ELEMENTS -----
|
|
902
|
-
// Basic form controls and interactive elements
|
|
903
|
-
'button',
|
|
904
|
-
'button svg', // SVG inside buttons are also clickable
|
|
905
|
-
'input',
|
|
906
|
-
'textarea',
|
|
907
|
-
'a',
|
|
908
|
-
'select',
|
|
909
|
-
'summary', // Disclosure (details/summary) elements
|
|
910
|
-
'details',
|
|
911
|
-
'audio[controls]', // Media controls
|
|
912
|
-
'video[controls]',
|
|
913
|
-
// ----- ARIA ROLE-BASED INTERACTIVE ELEMENTS -----
|
|
914
|
-
// Elements with explicit interactive ARIA roles
|
|
915
|
-
'[role="button"]',
|
|
916
|
-
'[role="option"]',
|
|
917
|
-
'[role="link"]',
|
|
918
|
-
'[role="checkbox"]',
|
|
919
|
-
'[role="radio"]',
|
|
920
|
-
'[role="tab"]',
|
|
921
|
-
'[role="menuitem"]',
|
|
922
|
-
'[role="menuitemcheckbox"]',
|
|
923
|
-
'[role="menuitemradio"]',
|
|
924
|
-
'[role="combobox"]',
|
|
925
|
-
'[role="listbox"]',
|
|
926
|
-
'[role="searchbox"]',
|
|
927
|
-
'[role="spinbutton"]',
|
|
928
|
-
'[role="slider"]',
|
|
929
|
-
'[role="switch"]',
|
|
930
|
-
'[role="menu"]',
|
|
931
|
-
'[role="menubar"]',
|
|
932
|
-
'[role="treeitem"]',
|
|
933
|
-
// ----- INTERACTIVE ARIA ATTRIBUTES -----
|
|
934
|
-
// Elements with popup behavior
|
|
935
|
-
'[aria-haspopup]',
|
|
936
|
-
'[aria-controls]',
|
|
937
|
-
'[aria-expanded]', // Elements that can expand/collapse
|
|
938
|
-
'[aria-pressed]', // Toggle buttons
|
|
939
|
-
'[aria-selected]', // Selectable items
|
|
940
|
-
// ----- EDITABLE AND FOCUSABLE ELEMENTS -----
|
|
941
|
-
// Elements that can receive input
|
|
942
|
-
'[contenteditable="true"]',
|
|
943
|
-
// Elements that can be dragged
|
|
944
|
-
'[draggable="true"]',
|
|
945
|
-
// Elements with explicit focus capability
|
|
946
|
-
'[tabindex]:not([tabindex="-1"])',
|
|
947
|
-
// ----- CSS FRAMEWORK-SPECIFIC CLASSES -----
|
|
948
|
-
// Bootstrap & similar frameworks
|
|
949
|
-
'.btn', // Standard button class
|
|
950
|
-
'.dropdown-toggle', // Dropdown triggers
|
|
951
|
-
'.nav-link', // Navigation links
|
|
952
|
-
'.page-link', // Pagination links
|
|
953
|
-
'.card-header[data-toggle="collapse"]', // Collapsible headers
|
|
954
|
-
'.accordion-button', // Accordion toggles
|
|
955
|
-
'.close', // Close buttons
|
|
956
|
-
'.modal-close', // Modal close buttons
|
|
957
|
-
// Material Design & Angular Material
|
|
958
|
-
'.mdc-button',
|
|
959
|
-
'.mat-button',
|
|
960
|
-
'.mat-icon-button',
|
|
961
|
-
'.mat-fab',
|
|
962
|
-
'.mat-mini-fab',
|
|
963
|
-
'.mat-menu-item',
|
|
964
|
-
'.mat-tab-label',
|
|
965
|
-
// Foundation
|
|
966
|
-
'.button',
|
|
967
|
-
'.menu-item',
|
|
968
|
-
'.accordion-title',
|
|
969
|
-
// Tailwind UI & Headless UI components
|
|
970
|
-
'.tw-button',
|
|
971
|
-
'[x-on\\:click]', // Alpine.js click handlers
|
|
972
|
-
// Vue-based frameworks
|
|
973
|
-
'.v-btn', // Vuetify
|
|
974
|
-
'.el-button', // Element UI
|
|
975
|
-
// React-based frameworks
|
|
976
|
-
'.ant-btn', // Ant Design
|
|
977
|
-
'.chakra-button', // Chakra UI
|
|
978
|
-
'.mui-button', // Material-UI
|
|
979
|
-
// ----- CUSTOM INTERACTIVE PATTERNS -----
|
|
980
|
-
// Common custom interactive classes
|
|
981
|
-
'.clickable',
|
|
982
|
-
'.selectable',
|
|
983
|
-
'.interactive',
|
|
984
|
-
'.toggle',
|
|
985
|
-
'.expandable',
|
|
986
|
-
'.switch',
|
|
987
|
-
'.slider',
|
|
988
|
-
// Common dropdown/select libraries
|
|
989
|
-
'.select2-selection',
|
|
990
|
-
'.chosen-single',
|
|
991
|
-
'.vs__dropdown-toggle', // Vue Select
|
|
992
|
-
// Tabs and accordion components
|
|
993
|
-
'.tab',
|
|
994
|
-
'.tab-header',
|
|
995
|
-
'.tab-title',
|
|
996
|
-
'.accordion-header',
|
|
997
|
-
// Mobile-friendly interactive elements
|
|
998
|
-
'.swipe-item',
|
|
999
|
-
'.touch-target',
|
|
1000
|
-
// ----- COMMON COMPONENT PATTERNS -----
|
|
1001
|
-
// Cards and tiles that are often clickable
|
|
1002
|
-
'.card[onclick]',
|
|
1003
|
-
'.tile[onclick]',
|
|
1004
|
-
'.card a', // Links inside cards
|
|
1005
|
-
// Social media & e-commerce patterns
|
|
1006
|
-
'.share-button',
|
|
1007
|
-
'.like-button',
|
|
1008
|
-
'.add-to-cart',
|
|
1009
|
-
'.product-card',
|
|
1010
|
-
// Notification and alert controls
|
|
1011
|
-
'.alert .close',
|
|
1012
|
-
'.toast .close',
|
|
1013
|
-
'.notification-action',
|
|
1014
|
-
].join(', ');
|
|
1015
|
-
let elements = Array.from(root.querySelectorAll(selector));
|
|
1016
|
-
// Dive into shadow roots
|
|
1017
|
-
root.querySelectorAll('*').forEach((el) => {
|
|
1018
|
-
if (el.shadowRoot) {
|
|
1019
|
-
// Recurse
|
|
1020
|
-
elements = elements.concat(getAllInteractableElements(el.shadowRoot));
|
|
1021
|
-
}
|
|
168
|
+
static async getScrolledDownScreenShot(page) {
|
|
169
|
+
try {
|
|
170
|
+
// Get the current scroll position and viewport height
|
|
171
|
+
const { scrollX, scrollY, viewportHeight } = await page.evaluate(() => {
|
|
172
|
+
return {
|
|
173
|
+
scrollX: window.scrollX,
|
|
174
|
+
scrollY: window.scrollY,
|
|
175
|
+
viewportHeight: window.innerHeight,
|
|
176
|
+
};
|
|
1022
177
|
});
|
|
1023
|
-
//
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
//
|
|
1032
|
-
.
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
const forId = elToCheck.htmlFor;
|
|
1068
|
-
const associatedInput = document.getElementById(forId);
|
|
1069
|
-
if (associatedInput) {
|
|
1070
|
-
associatedInput.setAttribute(interactableAttribute, offset.toString());
|
|
1071
|
-
offset++;
|
|
178
|
+
// Step 1: Try to scroll the window down by one viewport
|
|
179
|
+
await page.evaluate((viewportHeight) => {
|
|
180
|
+
window.scrollBy(0, viewportHeight);
|
|
181
|
+
}, viewportHeight);
|
|
182
|
+
// Check if the page actually scrolled
|
|
183
|
+
const newScrollY = await page.evaluate(() => window.scrollY);
|
|
184
|
+
// If the window scrolled successfully, take the screenshot and return
|
|
185
|
+
if (newScrollY > scrollY) {
|
|
186
|
+
// Take the screenshot
|
|
187
|
+
const screenshot = await PlaywrightUtils.takeScreenshot(page);
|
|
188
|
+
// Scroll back to the original position
|
|
189
|
+
await page.evaluate(([x, y]) => {
|
|
190
|
+
window.scrollTo(x, y);
|
|
191
|
+
}, [scrollX, scrollY]);
|
|
192
|
+
return screenshot;
|
|
193
|
+
}
|
|
194
|
+
// Step 2: If window didn't scroll, try to find scrollable elements
|
|
195
|
+
const didScrollElement = await page.evaluate(() => {
|
|
196
|
+
// Find all potentially scrollable elements
|
|
197
|
+
function findScrollableElements() {
|
|
198
|
+
const allElements = document.querySelectorAll('*');
|
|
199
|
+
const scrollableElements = [];
|
|
200
|
+
allElements.forEach((el) => {
|
|
201
|
+
const style = window.getComputedStyle(el);
|
|
202
|
+
const overflowY = style.getPropertyValue('overflow-y');
|
|
203
|
+
const overflowX = style.getPropertyValue('overflow-x');
|
|
204
|
+
const overflow = style.getPropertyValue('overflow');
|
|
205
|
+
// Check if the element has scrollable properties
|
|
206
|
+
if (overflowY === 'auto' ||
|
|
207
|
+
overflowY === 'scroll' ||
|
|
208
|
+
overflowX === 'auto' ||
|
|
209
|
+
overflowX === 'scroll' ||
|
|
210
|
+
overflow === 'auto' ||
|
|
211
|
+
overflow === 'scroll') {
|
|
212
|
+
// Additional check to see if the element actually has content to scroll
|
|
213
|
+
const scrollHeight = el.scrollHeight;
|
|
214
|
+
const clientHeight = el.clientHeight;
|
|
215
|
+
if (scrollHeight > clientHeight) {
|
|
216
|
+
scrollableElements.push({
|
|
217
|
+
element: el,
|
|
218
|
+
area: el.clientWidth * el.clientHeight, // Calculate area for sorting
|
|
219
|
+
scrollTop: el.scrollTop, // Save original scroll position
|
|
220
|
+
});
|
|
221
|
+
}
|
|
1072
222
|
}
|
|
1073
|
-
|
|
223
|
+
});
|
|
224
|
+
return scrollableElements;
|
|
225
|
+
}
|
|
226
|
+
// Find and sort scrollable elements by size (largest first)
|
|
227
|
+
const scrollableElements = findScrollableElements().sort((a, b) => b.area - a.area);
|
|
228
|
+
let didScroll = false;
|
|
229
|
+
// Try to scroll each element until one successfully scrolls
|
|
230
|
+
for (const { element } of scrollableElements) {
|
|
231
|
+
const originalScrollTop = element.scrollTop;
|
|
232
|
+
// Try to scroll down by the element's client height
|
|
233
|
+
element.scrollTop += element.clientHeight;
|
|
234
|
+
// Check if scrolling was successful
|
|
235
|
+
if (element.scrollTop > originalScrollTop) {
|
|
236
|
+
didScroll = true;
|
|
237
|
+
// Save the element in the window object so we can access it later
|
|
238
|
+
// @ts-ignore
|
|
239
|
+
window.__lastScrolledElement = element;
|
|
240
|
+
// @ts-ignore
|
|
241
|
+
window.__lastScrolledElementOriginalPosition = originalScrollTop;
|
|
1074
242
|
break;
|
|
1075
243
|
}
|
|
1076
|
-
elToCheck = elToCheck.parentElement;
|
|
1077
244
|
}
|
|
1078
|
-
|
|
1079
|
-
|
|
245
|
+
return didScroll;
|
|
246
|
+
});
|
|
247
|
+
// If no element could be scrolled, return undefined
|
|
248
|
+
if (!didScrollElement) {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
// Take a screenshot after scrolling the element
|
|
252
|
+
const screenshot = await PlaywrightUtils.takeScreenshot(page);
|
|
253
|
+
// Restore the original scroll position of the element
|
|
254
|
+
await page.evaluate(() => {
|
|
255
|
+
if (
|
|
256
|
+
// @ts-ignore
|
|
257
|
+
window.__lastScrolledElement &&
|
|
258
|
+
// @ts-ignore
|
|
259
|
+
window.__lastScrolledElementOriginalPosition !== undefined) {
|
|
260
|
+
// @ts-ignore
|
|
261
|
+
window.__lastScrolledElement.scrollTop = window.__lastScrolledElementOriginalPosition;
|
|
262
|
+
// Clean up
|
|
263
|
+
// @ts-ignore
|
|
264
|
+
delete window.__lastScrolledElement;
|
|
265
|
+
// @ts-ignore
|
|
266
|
+
delete window.__lastScrolledElementOriginalPosition;
|
|
1080
267
|
}
|
|
268
|
+
});
|
|
269
|
+
return screenshot;
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
if (PlaywrightUtils.isPageClosedError(error)) {
|
|
273
|
+
throw new PageClosedException_1.PageClosedException();
|
|
1081
274
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
275
|
+
else {
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
1084
279
|
}
|
|
1085
280
|
}
|
|
1086
281
|
exports.PlaywrightUtils = PlaywrightUtils;
|
|
1087
|
-
|
|
1088
|
-
|
|
282
|
+
/**
|
|
283
|
+
* A JavaScript code snippet intended to run as an initialization
|
|
284
|
+
* script for `DonobuFlow` flows. This script helps flows handle/track
|
|
285
|
+
* prompts/confirmations. This is done specially since these browser actions
|
|
286
|
+
* pause the Javascript main thread and also cause issues with running various
|
|
287
|
+
* Playwright actions. See `DonobuFlow.onDialog(Dialog)` for details.
|
|
288
|
+
*/
|
|
1089
289
|
PlaywrightUtils.DIALOG_PROMPT_TRACKER_INIT_SCRIPT = MiscUtils_1.MiscUtils.getResourceFileAsString(path_1.default.join('dialog-prompt-tracker.js'));
|
|
290
|
+
/**
|
|
291
|
+
* Returns a JavaScript code snippet intended to run as an initialization
|
|
292
|
+
* script for `DonobuFlow` flows. This enables the {@link ReplayableInteraction}
|
|
293
|
+
* and {@link PageInteractionTracker.INIT_SCRIPT} to generate
|
|
294
|
+
* smart selectors.
|
|
295
|
+
*/
|
|
1090
296
|
PlaywrightUtils.SMART_SELECTOR_GENERATOR_INIT_SCRIPT = MiscUtils_1.MiscUtils.getResourceFileAsString(path_1.default.join('smart-selector-generator.js'));
|
|
1091
|
-
PlaywrightUtils.PAGE_INTERACTIONS_TRACKER_INIT_SCRIPT = MiscUtils_1.MiscUtils.getResourceFileAsString(path_1.default.join('page-interactions-tracker.js'));
|
|
1092
|
-
PlaywrightUtils.DONOBU_CONTROL_PANEL_INIT_SCRIPT = MiscUtils_1.MiscUtils.getResourceFileAsString(path_1.default.join('control-panel.js'));
|
|
1093
|
-
// WARNING: If the control panel ID is changed here, you must also change the
|
|
1094
|
-
// control-panel.js control panel ID value.
|
|
1095
|
-
PlaywrightUtils.DONOBU_CONTROL_PANEL_ELEMENT_ID = 'donobu-control-panel';
|
|
1096
|
-
PlaywrightUtils.DONOBU_CONTROL_PANEL_HEADLINE_ELEMENT_ID = 'donobu-control-panel-title-text';
|
|
1097
|
-
PlaywrightUtils.DONOBU_INTERACTABLE_ATTRIBUTE = 'data-donobu-interactable';
|
|
1098
|
-
PlaywrightUtils.DONOBU_ANNOTATION_ATTRIBUTE = 'data-donobu-annotation';
|
|
1099
297
|
//# sourceMappingURL=PlaywrightUtils.js.map
|