@auto-wiz/playwright 1.0.0 → 1.0.1

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 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 getLocator;
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.getLocator(page, step);
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.getLocator(page, step);
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,35 @@ class PlaywrightFlowRunner {
57
84
  break;
58
85
  }
59
86
  case "select": {
60
- const locator = this.getLocator(page, step);
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.getLocator(page, step);
68
- const text = await locator.textContent({ timeout });
94
+ const locator = await this.resolveLocator(page, step, timeout);
95
+ let text = null;
96
+ if (step.prop === "outerHTML") {
97
+ text = await locator.evaluate((el) => el.outerHTML);
98
+ }
99
+ else if (step.prop === "value") {
100
+ text = await locator.inputValue();
101
+ }
102
+ else if (step.prop === "innerText") {
103
+ text = await locator.innerText({ timeout });
104
+ }
105
+ else {
106
+ // Default match 'outerHTML' if prop is not specified
107
+ text = await locator.evaluate((el) => el.outerHTML);
108
+ }
69
109
  return { success: true, extractedData: text?.trim() };
70
110
  }
71
111
  case "waitFor": {
72
112
  if (step.selector || step.locator) {
73
- const locator = this.getLocator(page, step);
74
- await locator.waitFor({
75
- state: "visible",
76
- timeout: step.timeoutMs || timeout,
77
- });
113
+ // resolveLocator internally waits for visibility, so this is implicitly handled,
114
+ // but we call it to ensure we find the valid element.
115
+ await this.resolveLocator(page, step, step.timeoutMs || timeout);
78
116
  }
79
117
  else if (step.timeoutMs) {
80
118
  await page.waitForTimeout(step.timeoutMs);
@@ -88,15 +126,54 @@ class PlaywrightFlowRunner {
88
126
  return { success: false, error: error.message };
89
127
  }
90
128
  }
91
- getLocator(page, step) {
129
+ async resolveLocator(page, step, timeout) {
130
+ const candidates = [];
131
+ // 1. Gather all candidate selectors
92
132
  if ("locator" in step && step.locator) {
93
- const { primary } = step.locator;
94
- return page.locator(primary).first();
133
+ const { primary, fallbacks = [] } = step.locator;
134
+ candidates.push(primary, ...fallbacks);
135
+ }
136
+ else if ("selector" in step && step.selector) {
137
+ candidates.push(step.selector);
95
138
  }
96
- if ("selector" in step && step.selector) {
97
- return page.locator(step.selector).first();
139
+ else {
140
+ throw new Error(`Step ${step.type} requires a selector or locator`);
141
+ }
142
+ if (candidates.length === 0) {
143
+ throw new Error(`Step ${step.type} has no valid selectors`);
144
+ }
145
+ // 2. If only one candidate, just return it (Playwright's default behavior)
146
+ if (candidates.length === 1) {
147
+ return page.locator(candidates[0]).first();
148
+ }
149
+ // 3. Parallel Race: Check all candidates for visibility
150
+ // We create a promise for each candidate that resolves if the element becomes visible
151
+ // and returns the corresponding Locator.
152
+ const promises = candidates.map(async (selector) => {
153
+ const loc = page.locator(selector).first();
154
+ try {
155
+ // Wait for it to be visible.
156
+ // We use the full timeout for each parallel check.
157
+ // The first one to succeed wins.
158
+ await loc.waitFor({ state: "visible", timeout });
159
+ return loc;
160
+ }
161
+ catch (err) {
162
+ // If this specific selector fails, we throw so Promise.any ignores it
163
+ // (unless all fail)
164
+ throw err;
165
+ }
166
+ });
167
+ try {
168
+ // Promise.any resolves with the first fulfilled promise.
169
+ // If all reject, it throws an AggregateError.
170
+ const winner = await Promise.any(promises);
171
+ return winner;
172
+ }
173
+ catch (error) {
174
+ // If all failed, throw a descriptive error
175
+ throw new Error(`Failed to resolve locator. Tried candidates: ${JSON.stringify(candidates)}. Error: ${error.message}`);
98
176
  }
99
- throw new Error(`Step ${step.type} requires a selector or locator`);
100
177
  }
101
178
  }
102
179
  exports.PlaywrightFlowRunner = PlaywrightFlowRunner;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@auto-wiz/playwright",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
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.0.0"
27
+ "@auto-wiz/core": "1.0.1"
28
28
  },
29
29
  "devDependencies": {
30
30
  "typescript": "^5.0.0",