diff --git a/docs/src/api/class-frame.md b/docs/src/api/class-frame.md index 929d1453cc..0528c4cfa6 100644 --- a/docs/src/api/class-frame.md +++ b/docs/src/api/class-frame.md @@ -754,6 +754,42 @@ var contentFrame = await frameElement.ContentFrameAsync(); Console.WriteLine(frame == contentFrame); // -> True ``` + +## method: Frame.frameLocator +- returns: <[FrameLocator]> + +When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements +in that iframe. Following snippet locates element with text "Submit" in the iframe with id `my-frame`, +like `', + contentType: 'text/html' + }).catch(() => {}); + }); + await page.route('**/iframe.html', route => { + route.fulfill({ + body: ` + +
+ + +
+ 1 + 2 + `, + contentType: 'text/html' + }).catch(() => {}); + }); + await page.route('**/iframe-2.html', route => { + route.fulfill({ + body: '', + contentType: 'text/html' + }).catch(() => {}); + }); +} + +it('should work for iframe', async ({ page, server }) => { + await routeIframe(page); + await page.goto(server.EMPTY_PAGE); + const button = page.frameLocator('iframe').locator('button'); + await button.waitFor(); + expect(await button.innerText()).toBe('Hello iframe'); + await expect(button).toHaveText('Hello iframe'); + await button.click(); +}); + +it('should work for nested iframe', async ({ page, server }) => { + await routeIframe(page); + await page.goto(server.EMPTY_PAGE); + const button = page.frameLocator('iframe').frameLocator('iframe').locator('button'); + await button.waitFor(); + expect(await button.innerText()).toBe('Hello nested iframe'); + await expect(button).toHaveText('Hello nested iframe'); + await button.click(); +}); + +it('should work for $ and $$', async ({ page, server }) => { + await routeIframe(page); + await page.goto(server.EMPTY_PAGE); + const locator = page.frameLocator('iframe').locator('button'); + await expect(locator).toHaveText('Hello iframe'); + const spans = page.frameLocator('iframe').locator('span'); + await expect(spans).toHaveCount(2); +}); + +it('should wait for frame', async ({ page, server }) => { + await page.goto(server.EMPTY_PAGE); + const error = await page.frameLocator('iframe').locator('span').click({ timeout: 300 }).catch(e => e); + expect(error.message).toContain('waiting for frame "iframe"'); +}); + +it('should wait for frame 2', async ({ page, server }) => { + await routeIframe(page); + setTimeout(() => page.goto(server.EMPTY_PAGE).catch(() => {}), 300); + await page.frameLocator('iframe').locator('button').click(); +}); + +it('should wait for frame to go', async ({ page, server }) => { + await routeIframe(page); + await page.goto(server.EMPTY_PAGE); + setTimeout(() => page.$eval('iframe', e => e.remove()).catch(() => {}), 300); + await expect(page.frameLocator('iframe').locator('button')).toBeHidden(); +}); + +it('should not wait for frame', async ({ page, server }) => { + await page.goto(server.EMPTY_PAGE); + await expect(page.frameLocator('iframe').locator('span')).toBeHidden(); +}); + +it('should not wait for frame 2', async ({ page, server }) => { + await page.goto(server.EMPTY_PAGE); + await expect(page.frameLocator('iframe').locator('span')).not.toBeVisible(); +}); + +it('should not wait for frame 3', async ({ page, server }) => { + await page.goto(server.EMPTY_PAGE); + await expect(page.frameLocator('iframe').locator('span')).toHaveCount(0); +}); + +it('should click in lazy iframe', async ({ page, server }) => { + await page.route('**/iframe.html', route => { + route.fulfill({ + body: '', + contentType: 'text/html' + }).catch(() => {}); + }); + + // empty pge + await page.goto(server.EMPTY_PAGE); + + // add blank iframe + setTimeout(() => { + page.evaluate(() => { + const iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + }); + // navigate iframe + setTimeout(() => { + page.evaluate(() => document.querySelector('iframe').src = 'iframe.html'); + }, 500); + }, 500); + + // Click in iframe + const button = page.frameLocator('iframe').locator('button'); + const [, text] = await Promise.all([ + button.click(), + button.innerText(), + expect(button).toHaveText('Hello iframe') + ]); + expect(text).toBe('Hello iframe'); +}); + +it('waitFor should survive frame reattach', async ({ page, server }) => { + await routeIframe(page); + await page.goto(server.EMPTY_PAGE); + const button = page.frameLocator('iframe').locator('button:has-text("Hello nested iframe")'); + const promise = button.waitFor(); + await page.locator('iframe').evaluate(e => e.remove()); + await page.evaluate(() => { + const iframe = document.createElement('iframe'); + iframe.src = 'iframe-2.html'; + document.body.appendChild(iframe); + }); + await promise; +}); + +it('click should survive frame reattach', async ({ page, server }) => { + await routeIframe(page); + await page.goto(server.EMPTY_PAGE); + const button = page.frameLocator('iframe').locator('button:has-text("Hello nested iframe")'); + const promise = button.click(); + await page.locator('iframe').evaluate(e => e.remove()); + await page.evaluate(() => { + const iframe = document.createElement('iframe'); + iframe.src = 'iframe-2.html'; + document.body.appendChild(iframe); + }); + await promise; +}); + +it('click should survive iframe navigation', async ({ page, server }) => { + await routeIframe(page); + await page.goto(server.EMPTY_PAGE); + const button = page.frameLocator('iframe').locator('button:has-text("Hello nested iframe")'); + const promise = button.click(); + page.locator('iframe').evaluate(e => (e as HTMLIFrameElement).src = 'iframe-2.html'); + await promise; +}); + +it('should non work for non-frame', async ({ page, server }) => { + await routeIframe(page); + await page.setContent('
'); + const button = page.frameLocator('div').locator('button'); + const error = await button.waitFor().catch(e => e); + expect(error.message).toContain('
'); + expect(error.message).toContain('