@auto-wiz/playwright 1.0.0 → 1.1.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/runner.d.ts +1 -1
- package/dist/runner.js +95 -16
- package/package.json +2 -2
package/dist/runner.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ import { Page } from "playwright";
|
|
|
3
3
|
export declare class PlaywrightFlowRunner implements FlowRunner<Page> {
|
|
4
4
|
run(flow: Flow, page: Page, options?: RunnerOptions): Promise<RunResult>;
|
|
5
5
|
runStep(step: Step, page: Page, options?: RunnerOptions): Promise<ExecutionResult>;
|
|
6
|
-
private
|
|
6
|
+
private resolveLocator;
|
|
7
7
|
}
|
package/dist/runner.js
CHANGED
|
@@ -4,6 +4,33 @@ exports.PlaywrightFlowRunner = void 0;
|
|
|
4
4
|
class PlaywrightFlowRunner {
|
|
5
5
|
async run(flow, page, options = {}) {
|
|
6
6
|
const extractedData = {};
|
|
7
|
+
// Implicit Navigation:
|
|
8
|
+
// If we are on a blank page and the first step has a URL but is NOT a 'navigate' step,
|
|
9
|
+
// we should navigate to that URL to start the flow.
|
|
10
|
+
if (page.url() === "about:blank" && flow.steps.length > 0) {
|
|
11
|
+
const firstStep = flow.steps[0];
|
|
12
|
+
// Check if the step has a url property (it might not be in the Step type definition for all types)
|
|
13
|
+
if (firstStep.type !== "navigate" &&
|
|
14
|
+
"url" in firstStep &&
|
|
15
|
+
firstStep.url) {
|
|
16
|
+
try {
|
|
17
|
+
// Use the provided timeout or default 5s
|
|
18
|
+
await page.goto(firstStep.url, {
|
|
19
|
+
timeout: options.timeout || 5000,
|
|
20
|
+
waitUntil: "domcontentloaded",
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
// If navigation fails, we return early as the flow cannot proceed
|
|
25
|
+
return {
|
|
26
|
+
success: false,
|
|
27
|
+
error: `Implicit navigation failed: ${error.message}`,
|
|
28
|
+
failedStepIndex: 0,
|
|
29
|
+
extractedData,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
7
34
|
for (const [index, step] of flow.steps.entries()) {
|
|
8
35
|
try {
|
|
9
36
|
const result = await this.runStep(step, page, options);
|
|
@@ -43,12 +70,12 @@ class PlaywrightFlowRunner {
|
|
|
43
70
|
}
|
|
44
71
|
break;
|
|
45
72
|
case "click": {
|
|
46
|
-
const locator = this.
|
|
73
|
+
const locator = await this.resolveLocator(page, step, timeout);
|
|
47
74
|
await locator.click({ timeout });
|
|
48
75
|
break;
|
|
49
76
|
}
|
|
50
77
|
case "type": {
|
|
51
|
-
const locator = this.
|
|
78
|
+
const locator = await this.resolveLocator(page, step, timeout);
|
|
52
79
|
const text = step.text || step.originalText || "";
|
|
53
80
|
await locator.fill(text, { timeout });
|
|
54
81
|
if (step.submit) {
|
|
@@ -57,24 +84,37 @@ class PlaywrightFlowRunner {
|
|
|
57
84
|
break;
|
|
58
85
|
}
|
|
59
86
|
case "select": {
|
|
60
|
-
const locator = this.
|
|
87
|
+
const locator = await this.resolveLocator(page, step, timeout);
|
|
61
88
|
if (step.value) {
|
|
62
89
|
await locator.selectOption(step.value, { timeout });
|
|
63
90
|
}
|
|
64
91
|
break;
|
|
65
92
|
}
|
|
66
93
|
case "extract": {
|
|
67
|
-
const locator = this.
|
|
68
|
-
|
|
94
|
+
const locator = await this.resolveLocator(page, step, timeout);
|
|
95
|
+
let text = null;
|
|
96
|
+
if (step.prop === "value") {
|
|
97
|
+
text = await locator.inputValue();
|
|
98
|
+
}
|
|
99
|
+
else if (step.prop === "innerText") {
|
|
100
|
+
text = await locator.innerText({ timeout });
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// Default match 'outerHTML' if prop is not specified or explicit 'outerHTML'
|
|
104
|
+
text = await locator.evaluate((el) => {
|
|
105
|
+
const clone = el.cloneNode(true);
|
|
106
|
+
const svgs = clone.querySelectorAll("svg");
|
|
107
|
+
svgs.forEach((svg) => svg.remove());
|
|
108
|
+
return clone.outerHTML;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
69
111
|
return { success: true, extractedData: text?.trim() };
|
|
70
112
|
}
|
|
71
113
|
case "waitFor": {
|
|
72
114
|
if (step.selector || step.locator) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
timeout: step.timeoutMs || timeout,
|
|
77
|
-
});
|
|
115
|
+
// resolveLocator internally waits for visibility, so this is implicitly handled,
|
|
116
|
+
// but we call it to ensure we find the valid element.
|
|
117
|
+
await this.resolveLocator(page, step, step.timeoutMs || timeout);
|
|
78
118
|
}
|
|
79
119
|
else if (step.timeoutMs) {
|
|
80
120
|
await page.waitForTimeout(step.timeoutMs);
|
|
@@ -88,15 +128,54 @@ class PlaywrightFlowRunner {
|
|
|
88
128
|
return { success: false, error: error.message };
|
|
89
129
|
}
|
|
90
130
|
}
|
|
91
|
-
|
|
131
|
+
async resolveLocator(page, step, timeout) {
|
|
132
|
+
const candidates = [];
|
|
133
|
+
// 1. Gather all candidate selectors
|
|
92
134
|
if ("locator" in step && step.locator) {
|
|
93
|
-
const { primary } = step.locator;
|
|
94
|
-
|
|
135
|
+
const { primary, fallbacks = [] } = step.locator;
|
|
136
|
+
candidates.push(primary, ...fallbacks);
|
|
137
|
+
}
|
|
138
|
+
else if ("selector" in step && step.selector) {
|
|
139
|
+
candidates.push(step.selector);
|
|
95
140
|
}
|
|
96
|
-
|
|
97
|
-
|
|
141
|
+
else {
|
|
142
|
+
throw new Error(`Step ${step.type} requires a selector or locator`);
|
|
143
|
+
}
|
|
144
|
+
if (candidates.length === 0) {
|
|
145
|
+
throw new Error(`Step ${step.type} has no valid selectors`);
|
|
146
|
+
}
|
|
147
|
+
// 2. If only one candidate, just return it (Playwright's default behavior)
|
|
148
|
+
if (candidates.length === 1) {
|
|
149
|
+
return page.locator(candidates[0]).first();
|
|
150
|
+
}
|
|
151
|
+
// 3. Parallel Race: Check all candidates for visibility
|
|
152
|
+
// We create a promise for each candidate that resolves if the element becomes visible
|
|
153
|
+
// and returns the corresponding Locator.
|
|
154
|
+
const promises = candidates.map(async (selector) => {
|
|
155
|
+
const loc = page.locator(selector).first();
|
|
156
|
+
try {
|
|
157
|
+
// Wait for it to be visible.
|
|
158
|
+
// We use the full timeout for each parallel check.
|
|
159
|
+
// The first one to succeed wins.
|
|
160
|
+
await loc.waitFor({ state: "visible", timeout });
|
|
161
|
+
return loc;
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
// If this specific selector fails, we throw so Promise.any ignores it
|
|
165
|
+
// (unless all fail)
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
try {
|
|
170
|
+
// Promise.any resolves with the first fulfilled promise.
|
|
171
|
+
// If all reject, it throws an AggregateError.
|
|
172
|
+
const winner = await Promise.any(promises);
|
|
173
|
+
return winner;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
// If all failed, throw a descriptive error
|
|
177
|
+
throw new Error(`Failed to resolve locator. Tried candidates: ${JSON.stringify(candidates)}. Error: ${error.message}`);
|
|
98
178
|
}
|
|
99
|
-
throw new Error(`Step ${step.type} requires a selector or locator`);
|
|
100
179
|
}
|
|
101
180
|
}
|
|
102
181
|
exports.PlaywrightFlowRunner = PlaywrightFlowRunner;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@auto-wiz/playwright",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "JaeSang",
|
|
6
6
|
"repository": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"playwright": "^1.40.0",
|
|
27
|
-
"@auto-wiz/core": "1.
|
|
27
|
+
"@auto-wiz/core": "1.1.0"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"typescript": "^5.0.0",
|