/** * @license * Copyright 2023 Google Inc. * SPDX-License-Identifier: Apache-2.0 */ import expect from 'expect'; import {TimeoutError} from 'puppeteer-core'; import { Locator, LocatorEvent, } from 'puppeteer-core/internal/api/locators/locators.js'; import sinon from 'sinon'; import {getTestState, setupTestBrowserHooks} from './mocha-utils.js'; describe('Locator', function () { setupTestBrowserHooks(); it('should work with a frame', async () => { const {page} = await getTestState(); await page.setViewport({width: 500, height: 500}); await page.setContent(` `); let willClick = false; await page .mainFrame() .locator('button') .on(LocatorEvent.Action, () => { willClick = true; }) .click(); using button = await page.$('button'); const text = await button?.evaluate(el => { return el.innerText; }); expect(text).toBe('clicked'); expect(willClick).toBe(true); }); it('should work without preconditions', async () => { const {page} = await getTestState(); await page.setViewport({width: 500, height: 500}); await page.setContent(` `); let willClick = false; await page .locator('button') .setEnsureElementIsInTheViewport(false) .setTimeout(0) .setVisibility(null) .setWaitForEnabled(false) .setWaitForStableBoundingBox(false) .on(LocatorEvent.Action, () => { willClick = true; }) .click(); using button = await page.$('button'); const text = await button?.evaluate(el => { return el.innerText; }); expect(text).toBe('clicked'); expect(willClick).toBe(true); }); describe('Locator.click', function () { it('should work', async () => { const {page} = await getTestState(); await page.setViewport({width: 500, height: 500}); await page.setContent(` `); let willClick = false; await page .locator('button') .on(LocatorEvent.Action, () => { willClick = true; }) .click(); using button = await page.$('button'); const text = await button?.evaluate(el => { return el.innerText; }); expect(text).toBe('clicked'); expect(willClick).toBe(true); }); it('should work for multiple selectors', async () => { const {page} = await getTestState(); await page.setViewport({width: 500, height: 500}); await page.setContent(` `); let clicked = false; await page .locator('::-p-text(test), ::-p-xpath(/button)') .on(LocatorEvent.Action, () => { clicked = true; }) .click(); using button = await page.$('button'); const text = await button?.evaluate(el => { return el.innerText; }); expect(text).toBe('clicked'); expect(clicked).toBe(true); }); it('should work if the element is out of viewport', async () => { const {page} = await getTestState(); await page.setViewport({width: 500, height: 500}); await page.setContent(` `); await page.locator('button').click(); using button = await page.$('button'); const text = await button?.evaluate(el => { return el.innerText; }); expect(text).toBe('clicked'); }); it('should work if the element becomes visible later', async () => { const {page} = await getTestState(); await page.setViewport({width: 500, height: 500}); await page.setContent(` `); using button = await page.$('button'); const result = page .locator('button') .click() .catch(err => { return err; }); expect( await button?.evaluate(el => { return el.innerText; }) ).toBe('test'); await button?.evaluate(el => { el.style.display = 'block'; }); const maybeError = await result; if (maybeError instanceof Error) { throw maybeError; } expect( await button?.evaluate(el => { return el.innerText; }) ).toBe('clicked'); }); it('should work if the element becomes enabled later', async () => { const {page} = await getTestState(); await page.setViewport({width: 500, height: 500}); await page.setContent(` `); using button = await page.$('button'); const result = page.locator('button').click(); expect( await button?.evaluate(el => { return el.innerText; }) ).toBe('test'); await button?.evaluate(el => { el.disabled = false; }); await result; expect( await button?.evaluate(el => { return el.innerText; }) ).toBe('clicked'); }); it('should work if multiple conditions are satisfied later', async () => { const {page} = await getTestState(); await page.setViewport({width: 500, height: 500}); await page.setContent(` `); using button = await page.$('button'); const result = page.locator('button').click(); expect( await button?.evaluate(el => { return el.innerText; }) ).toBe('test'); await button?.evaluate(el => { el.disabled = false; el.style.display = 'block'; }); await result; expect( await button?.evaluate(el => { return el.innerText; }) ).toBe('clicked'); }); it('should time out', async () => { const clock = sinon.useFakeTimers({ shouldClearNativeTimers: true, shouldAdvanceTime: true, }); try { const {page} = await getTestState(); page.setDefaultTimeout(5000); await page.setViewport({width: 500, height: 500}); await page.setContent(` `); const result = page.locator('button').click(); clock.tick(5100); await expect(result).rejects.toEqual( new TimeoutError('Timed out after waiting 5000ms') ); } finally { clock.restore(); } }); it('should retry clicks on errors', async () => { const {page} = await getTestState(); const clock = sinon.useFakeTimers({ shouldClearNativeTimers: true, shouldAdvanceTime: true, }); try { page.setDefaultTimeout(5000); await page.setViewport({width: 500, height: 500}); await page.setContent(` `); const result = page.locator('button').click(); clock.tick(5100); await expect(result).rejects.toEqual( new TimeoutError('Timed out after waiting 5000ms') ); } finally { clock.restore(); } }); it('can be aborted', async () => { const {page} = await getTestState(); const clock = sinon.useFakeTimers({ shouldClearNativeTimers: true, shouldAdvanceTime: true, }); try { page.setDefaultTimeout(5000); await page.setViewport({width: 500, height: 500}); await page.setContent(` `); const abortController = new AbortController(); const result = page.locator('button').click({ signal: abortController.signal, }); clock.tick(2000); abortController.abort(); await expect(result).rejects.toThrow(/aborted/); } finally { clock.restore(); } }); it('should work with a OOPIF', async () => { const {page} = await getTestState(); await page.setViewport({width: 500, height: 500}); await page.setContent(` `); const frame = await page.waitForFrame(frame => { return frame.url().startsWith('data'); }); let willClick = false; await frame .locator('button') .on(LocatorEvent.Action, () => { willClick = true; }) .click(); using button = await frame.$('button'); const text = await button?.evaluate(el => { return el.innerText; }); expect(text).toBe('clicked'); expect(willClick).toBe(true); }); }); describe('Locator.hover', function () { it('should work', async () => { const {page} = await getTestState(); await page.setViewport({width: 500, height: 500}); await page.setContent(` `); let hovered = false; await page .locator('button') .on(LocatorEvent.Action, () => { hovered = true; }) .hover(); using button = await page.$('button'); const text = await button?.evaluate(el => { return el.innerText; }); expect(text).toBe('hovered'); expect(hovered).toBe(true); }); }); describe('Locator.scroll', function () { it('should work', async () => { const {page} = await getTestState(); await page.setViewport({width: 500, height: 500}); await page.setContent(`