@arcadialdev/arcality 2.2.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/.agents/skills/e2e-testing-expert/SKILL.md +28 -0
- package/.agents/skills/frontend-design/LICENSE.txt +177 -0
- package/.agents/skills/frontend-design/SKILL.md +42 -0
- package/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
- package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
- package/.agents/skills/playwright-best-practices/LICENSE.md +7 -0
- package/.agents/skills/playwright-best-practices/README.md +147 -0
- package/.agents/skills/playwright-best-practices/SKILL.md +303 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +360 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication.md +871 -0
- package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +364 -0
- package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +409 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-context.md +288 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-user.md +393 -0
- package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +452 -0
- package/.agents/skills/playwright-best-practices/advanced/third-party.md +464 -0
- package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +363 -0
- package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +369 -0
- package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +383 -0
- package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +391 -0
- package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +403 -0
- package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +504 -0
- package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +403 -0
- package/.agents/skills/playwright-best-practices/core/annotations.md +424 -0
- package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +361 -0
- package/.agents/skills/playwright-best-practices/core/configuration.md +452 -0
- package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +417 -0
- package/.agents/skills/playwright-best-practices/core/global-setup.md +434 -0
- package/.agents/skills/playwright-best-practices/core/locators.md +242 -0
- package/.agents/skills/playwright-best-practices/core/page-object-model.md +315 -0
- package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +453 -0
- package/.agents/skills/playwright-best-practices/core/test-data.md +492 -0
- package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +361 -0
- package/.agents/skills/playwright-best-practices/core/test-tags.md +298 -0
- package/.agents/skills/playwright-best-practices/debugging/console-errors.md +420 -0
- package/.agents/skills/playwright-best-practices/debugging/debugging.md +504 -0
- package/.agents/skills/playwright-best-practices/debugging/error-testing.md +360 -0
- package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +496 -0
- package/.agents/skills/playwright-best-practices/frameworks/angular.md +530 -0
- package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +469 -0
- package/.agents/skills/playwright-best-practices/frameworks/react.md +531 -0
- package/.agents/skills/playwright-best-practices/frameworks/vue.md +574 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +468 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +283 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +546 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +397 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +521 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +371 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +453 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +424 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +497 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +359 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +719 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +506 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +493 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +500 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +576 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +509 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +377 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +562 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +561 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +331 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +508 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +476 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +430 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +634 -0
- package/.env.example +21 -0
- package/README.md +30 -0
- package/bin/arcality.mjs +86 -0
- package/package.json +66 -0
- package/playwright.config.ts +12 -0
- package/scripts/cleanup-qmsdev.mjs +63 -0
- package/scripts/discover-view.mjs +52 -0
- package/scripts/extract-view.mjs +64 -0
- package/scripts/gen-and-run.mjs +838 -0
- package/scripts/init.mjs +290 -0
- package/scripts/migrate-to-central-out.mjs +157 -0
- package/scripts/postinstall.mjs +63 -0
- package/scripts/rebrand-report.mjs +241 -0
- package/scripts/setup.mjs +166 -0
- package/src/KnowledgeService.ts +239 -0
- package/src/arcalityClient.mjs +266 -0
- package/src/configLoader.mjs +179 -0
- package/src/configManager.mjs +172 -0
- package/src/consoleBanner.ts +32 -0
- package/src/envSetup.ts +205 -0
- package/src/index.ts +25 -0
- package/src/projectInspector.ts +42 -0
- package/src/services/collectiveMemoryService.ts +178 -0
- package/src/testRunner.ts +201 -0
- package/tests/_helpers/ArcalityReporter.ts +490 -0
- package/tests/_helpers/agentic-runner.spec.ts +741 -0
- package/tests/_helpers/ai-agent-helper.ts +1573 -0
- package/tests/_helpers/discover-view.spec.ts +238 -0
- package/tests/_helpers/extract-view.spec.ts +118 -0
- package/tests/_helpers/qa-tools.ts +333 -0
- package/tests/_helpers/smart-action.spec.ts +1458 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# iFrame Testing
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
1. [Basic iFrame Access](#basic-iframe-access)
|
|
6
|
+
2. [Cross-Origin iFrames](#cross-origin-iframes)
|
|
7
|
+
3. [Nested iFrames](#nested-iframes)
|
|
8
|
+
4. [Dynamic iFrames](#dynamic-iframes)
|
|
9
|
+
5. [iFrame Navigation](#iframe-navigation)
|
|
10
|
+
6. [Common Patterns](#common-patterns)
|
|
11
|
+
|
|
12
|
+
## Basic iFrame Access
|
|
13
|
+
|
|
14
|
+
### Using frameLocator
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// Access iframe by selector
|
|
18
|
+
const frame = page.frameLocator("iframe#payment");
|
|
19
|
+
await frame.getByRole("button", { name: "Pay" }).click();
|
|
20
|
+
|
|
21
|
+
// Access by name attribute
|
|
22
|
+
const namedFrame = page.frameLocator('iframe[name="checkout"]');
|
|
23
|
+
await namedFrame.getByLabel("Card number").fill("4242424242424242");
|
|
24
|
+
|
|
25
|
+
// Access by title
|
|
26
|
+
const titledFrame = page.frameLocator('iframe[title="Payment Form"]');
|
|
27
|
+
|
|
28
|
+
// Access by src (partial match)
|
|
29
|
+
const srcFrame = page.frameLocator('iframe[src*="stripe.com"]');
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Frame vs FrameLocator
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// frameLocator - for locator-based operations (recommended)
|
|
36
|
+
const frameLocator = page.frameLocator("#my-iframe");
|
|
37
|
+
await frameLocator.getByRole("button").click();
|
|
38
|
+
|
|
39
|
+
// frame() - for Frame object operations (navigation, evaluation)
|
|
40
|
+
const frame = page.frame({ name: "my-frame" });
|
|
41
|
+
if (frame) {
|
|
42
|
+
await frame.goto("https://example.com");
|
|
43
|
+
const title = await frame.title();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Get all frames
|
|
47
|
+
const frames = page.frames();
|
|
48
|
+
for (const f of frames) {
|
|
49
|
+
console.log("Frame URL:", f.url());
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Waiting for iFrame Content
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// Wait for iframe to load
|
|
57
|
+
const frame = page.frameLocator("#dynamic-iframe");
|
|
58
|
+
|
|
59
|
+
// Wait for element inside iframe
|
|
60
|
+
await expect(frame.getByRole("heading")).toBeVisible({ timeout: 10000 });
|
|
61
|
+
|
|
62
|
+
// Wait for iframe src to change
|
|
63
|
+
await page.waitForFunction(() => {
|
|
64
|
+
const iframe = document.querySelector("iframe#my-frame") as HTMLIFrameElement;
|
|
65
|
+
return iframe?.src.includes("loaded");
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Cross-Origin iFrames
|
|
70
|
+
|
|
71
|
+
### Accessing Cross-Origin Content
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// Cross-origin iframes work seamlessly with frameLocator
|
|
75
|
+
const thirdPartyFrame = page.frameLocator('iframe[src*="third-party.com"]');
|
|
76
|
+
|
|
77
|
+
// Interact with elements inside cross-origin iframe
|
|
78
|
+
await thirdPartyFrame.getByRole("textbox").fill("test@example.com");
|
|
79
|
+
await thirdPartyFrame.getByRole("button", { name: "Submit" }).click();
|
|
80
|
+
|
|
81
|
+
// Wait for cross-origin iframe to be ready
|
|
82
|
+
await expect(thirdPartyFrame.locator("body")).toBeVisible();
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Payment Provider iFrames (Stripe, PayPal)
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
test("Stripe payment iframe", async ({ page }) => {
|
|
89
|
+
await page.goto("/checkout");
|
|
90
|
+
|
|
91
|
+
// Stripe uses multiple iframes for each field
|
|
92
|
+
const cardFrame = page
|
|
93
|
+
.frameLocator('iframe[name*="__privateStripeFrame"]')
|
|
94
|
+
.first();
|
|
95
|
+
|
|
96
|
+
// Wait for Stripe to initialize
|
|
97
|
+
await expect(cardFrame.locator('[placeholder="Card number"]')).toBeVisible({
|
|
98
|
+
timeout: 15000,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Fill card details
|
|
102
|
+
await cardFrame
|
|
103
|
+
.locator('[placeholder="Card number"]')
|
|
104
|
+
.fill("4242424242424242");
|
|
105
|
+
await cardFrame.locator('[placeholder="MM / YY"]').fill("12/30");
|
|
106
|
+
await cardFrame.locator('[placeholder="CVC"]').fill("123");
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Handling OAuth in iFrames
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
test("OAuth iframe flow", async ({ page }) => {
|
|
114
|
+
await page.goto("/login");
|
|
115
|
+
await page.getByRole("button", { name: "Sign in with Google" }).click();
|
|
116
|
+
|
|
117
|
+
// If OAuth opens in iframe instead of popup
|
|
118
|
+
const oauthFrame = page.frameLocator('iframe[src*="accounts.google.com"]');
|
|
119
|
+
|
|
120
|
+
// Wait for OAuth form
|
|
121
|
+
await expect(oauthFrame.getByLabel("Email")).toBeVisible({ timeout: 10000 });
|
|
122
|
+
await oauthFrame.getByLabel("Email").fill("test@gmail.com");
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Nested iFrames
|
|
127
|
+
|
|
128
|
+
### Accessing Nested Frames
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// Parent iframe contains child iframe
|
|
132
|
+
const parentFrame = page.frameLocator("#outer-frame");
|
|
133
|
+
const childFrame = parentFrame.frameLocator("#inner-frame");
|
|
134
|
+
|
|
135
|
+
// Interact with deeply nested content
|
|
136
|
+
await childFrame.getByRole("button", { name: "Submit" }).click();
|
|
137
|
+
|
|
138
|
+
// Multiple levels of nesting
|
|
139
|
+
const level1 = page.frameLocator("#level1");
|
|
140
|
+
const level2 = level1.frameLocator("#level2");
|
|
141
|
+
const level3 = level2.frameLocator("#level3");
|
|
142
|
+
await level3.getByText("Deep content").click();
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Finding Elements Across Frame Hierarchy
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// Helper to search all frames for an element
|
|
149
|
+
async function findInAnyFrame(
|
|
150
|
+
page: Page,
|
|
151
|
+
selector: string,
|
|
152
|
+
): Promise<Locator | null> {
|
|
153
|
+
// Check main page first
|
|
154
|
+
const mainCount = await page.locator(selector).count();
|
|
155
|
+
if (mainCount > 0) return page.locator(selector);
|
|
156
|
+
|
|
157
|
+
// Check all frames
|
|
158
|
+
for (const frame of page.frames()) {
|
|
159
|
+
const count = await frame.locator(selector).count();
|
|
160
|
+
if (count > 0) {
|
|
161
|
+
return frame.locator(selector);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
test("find element in any frame", async ({ page }) => {
|
|
168
|
+
await page.goto("/complex-page");
|
|
169
|
+
const element = await findInAnyFrame(page, '[data-testid="submit-btn"]');
|
|
170
|
+
if (element) await element.click();
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Dynamic iFrames
|
|
175
|
+
|
|
176
|
+
### iFrames Created at Runtime
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
test("handle dynamically created iframe", async ({ page }) => {
|
|
180
|
+
await page.goto("/dashboard");
|
|
181
|
+
|
|
182
|
+
// Click button that creates iframe
|
|
183
|
+
await page.getByRole("button", { name: "Open Widget" }).click();
|
|
184
|
+
|
|
185
|
+
// Wait for iframe to appear in DOM
|
|
186
|
+
await page.waitForSelector("iframe#widget-frame");
|
|
187
|
+
|
|
188
|
+
// Now access the frame
|
|
189
|
+
const widgetFrame = page.frameLocator("#widget-frame");
|
|
190
|
+
await expect(widgetFrame.getByText("Widget Loaded")).toBeVisible();
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### iFrames with Changing src
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
test("iframe src changes", async ({ page }) => {
|
|
198
|
+
await page.goto("/multi-step");
|
|
199
|
+
|
|
200
|
+
const frame = page.frameLocator("#step-frame");
|
|
201
|
+
|
|
202
|
+
// Step 1
|
|
203
|
+
await expect(frame.getByText("Step 1")).toBeVisible();
|
|
204
|
+
await frame.getByRole("button", { name: "Next" }).click();
|
|
205
|
+
|
|
206
|
+
// Wait for iframe to reload with new content
|
|
207
|
+
await expect(frame.getByText("Step 2")).toBeVisible({ timeout: 10000 });
|
|
208
|
+
await frame.getByRole("button", { name: "Next" }).click();
|
|
209
|
+
|
|
210
|
+
// Step 3
|
|
211
|
+
await expect(frame.getByText("Step 3")).toBeVisible({ timeout: 10000 });
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Lazy-Loaded iFrames
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
test("lazy loaded iframe", async ({ page }) => {
|
|
219
|
+
await page.goto("/page-with-lazy-iframe");
|
|
220
|
+
|
|
221
|
+
// Scroll to trigger lazy load
|
|
222
|
+
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
|
223
|
+
|
|
224
|
+
// Wait for iframe to load
|
|
225
|
+
const lazyFrame = page.frameLocator("#lazy-iframe");
|
|
226
|
+
await expect(lazyFrame.locator("body")).not.toBeEmpty({ timeout: 15000 });
|
|
227
|
+
|
|
228
|
+
// Interact with content
|
|
229
|
+
await lazyFrame.getByRole("button").click();
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## iFrame Navigation
|
|
234
|
+
|
|
235
|
+
### Navigating Within iFrame
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
test("iframe internal navigation", async ({ page }) => {
|
|
239
|
+
await page.goto("/app");
|
|
240
|
+
|
|
241
|
+
// Get frame object for navigation control
|
|
242
|
+
const frame = page.frame({ name: "content-frame" });
|
|
243
|
+
if (!frame) throw new Error("Frame not found");
|
|
244
|
+
|
|
245
|
+
// Navigate within iframe
|
|
246
|
+
await frame.goto("https://embedded-app.com/page2");
|
|
247
|
+
|
|
248
|
+
// Wait for navigation
|
|
249
|
+
await frame.waitForURL("**/page2");
|
|
250
|
+
|
|
251
|
+
// Verify content
|
|
252
|
+
await expect(frame.getByRole("heading")).toHaveText("Page 2");
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Handling Frame Navigation Events
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
test("track iframe navigation", async ({ page }) => {
|
|
260
|
+
const navigations: string[] = [];
|
|
261
|
+
|
|
262
|
+
// Listen to frame navigation
|
|
263
|
+
page.on("framenavigated", (frame) => {
|
|
264
|
+
if (frame.parentFrame()) {
|
|
265
|
+
// This is an iframe navigation
|
|
266
|
+
navigations.push(frame.url());
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
await page.goto("/with-iframe");
|
|
271
|
+
await page
|
|
272
|
+
.frameLocator("#nav-frame")
|
|
273
|
+
.getByRole("link", { name: "Page 2" })
|
|
274
|
+
.click();
|
|
275
|
+
|
|
276
|
+
// Verify navigation occurred
|
|
277
|
+
expect(navigations.some((url) => url.includes("page2"))).toBe(true);
|
|
278
|
+
});
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Common Patterns
|
|
282
|
+
|
|
283
|
+
### iFrame Fixture
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
// fixtures.ts
|
|
287
|
+
import { test as base, FrameLocator } from "@playwright/test";
|
|
288
|
+
|
|
289
|
+
export const test = base.extend<{ paymentFrame: FrameLocator }>({
|
|
290
|
+
paymentFrame: async ({ page }, use) => {
|
|
291
|
+
await page.goto("/checkout");
|
|
292
|
+
|
|
293
|
+
// Wait for payment iframe to be ready
|
|
294
|
+
const frame = page.frameLocator('iframe[src*="payment"]');
|
|
295
|
+
await expect(frame.locator("body")).toBeVisible({ timeout: 15000 });
|
|
296
|
+
|
|
297
|
+
await use(frame);
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// test file
|
|
302
|
+
test("complete payment", async ({ paymentFrame }) => {
|
|
303
|
+
await paymentFrame.getByLabel("Card").fill("4242424242424242");
|
|
304
|
+
await paymentFrame.getByRole("button", { name: "Pay" }).click();
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Debugging iFrame Issues
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
test("debug iframe content", async ({ page }) => {
|
|
312
|
+
await page.goto("/page-with-iframes");
|
|
313
|
+
|
|
314
|
+
// List all frames
|
|
315
|
+
console.log("All frames:");
|
|
316
|
+
for (const frame of page.frames()) {
|
|
317
|
+
console.log(` - ${frame.name() || "(unnamed)"}: ${frame.url()}`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Screenshot specific iframe content
|
|
321
|
+
const frame = page.frame({ name: "target-frame" });
|
|
322
|
+
if (frame) {
|
|
323
|
+
const body = frame.locator("body");
|
|
324
|
+
await body.screenshot({ path: "iframe-content.png" });
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Get iframe HTML for debugging
|
|
328
|
+
const frameContent = page.frameLocator("#my-frame");
|
|
329
|
+
const html = await frameContent.locator("body").innerHTML();
|
|
330
|
+
console.log("iFrame HTML:", html.substring(0, 500));
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Handling iFrame Load Failures
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
test("handle iframe load failure", async ({ page }) => {
|
|
338
|
+
await page.goto("/page-with-unreliable-iframe");
|
|
339
|
+
|
|
340
|
+
const frame = page.frameLocator("#unreliable-frame");
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
// Try to interact with iframe content
|
|
344
|
+
await expect(frame.getByRole("button")).toBeVisible({ timeout: 5000 });
|
|
345
|
+
await frame.getByRole("button").click();
|
|
346
|
+
} catch (error) {
|
|
347
|
+
// Fallback: refresh iframe
|
|
348
|
+
await page.evaluate(() => {
|
|
349
|
+
const iframe = document.querySelector(
|
|
350
|
+
"#unreliable-frame",
|
|
351
|
+
) as HTMLIFrameElement;
|
|
352
|
+
if (iframe) iframe.src = iframe.src;
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Retry
|
|
356
|
+
await expect(frame.getByRole("button")).toBeVisible({ timeout: 10000 });
|
|
357
|
+
await frame.getByRole("button").click();
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Mocking iFrame Content
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
test("mock iframe response", async ({ page }) => {
|
|
366
|
+
// Intercept iframe src request
|
|
367
|
+
await page.route("**/embedded-widget**", (route) => {
|
|
368
|
+
route.fulfill({
|
|
369
|
+
contentType: "text/html",
|
|
370
|
+
body: `
|
|
371
|
+
<!DOCTYPE html>
|
|
372
|
+
<html>
|
|
373
|
+
<body>
|
|
374
|
+
<h1>Mocked Widget</h1>
|
|
375
|
+
<button>Mocked Button</button>
|
|
376
|
+
</body>
|
|
377
|
+
</html>
|
|
378
|
+
`,
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
await page.goto("/page-with-widget");
|
|
383
|
+
|
|
384
|
+
const frame = page.frameLocator("#widget-frame");
|
|
385
|
+
await expect(frame.getByRole("heading")).toHaveText("Mocked Widget");
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## Anti-Patterns to Avoid
|
|
390
|
+
|
|
391
|
+
| Anti-Pattern | Problem | Solution |
|
|
392
|
+
| ------------------------------------- | --------------------------------- | -------------------------------------------------- |
|
|
393
|
+
| Using `page.frame()` for interactions | Less reliable than frameLocator | Use `page.frameLocator()` for element interactions |
|
|
394
|
+
| Hardcoding iframe index | Fragile if DOM order changes | Use name, id, or src attribute selectors |
|
|
395
|
+
| Not waiting for iframe load | Race conditions | Wait for element inside iframe to be visible |
|
|
396
|
+
| Assuming same-origin | Cross-origin has different timing | Always wait for iframe content explicitly |
|
|
397
|
+
| Ignoring nested iframes | Element not found | Chain frameLocator calls for nested frames |
|
|
398
|
+
|
|
399
|
+
## Related References
|
|
400
|
+
|
|
401
|
+
- **Locators**: See [locators.md](../core/locators.md) for selector strategies
|
|
402
|
+
- **Third-party services**: See [third-party.md](../advanced/third-party.md) for payment iframe patterns
|
|
403
|
+
- **Debugging**: See [debugging.md](../debugging/debugging.md) for troubleshooting iframe issues
|