From c9220801e7770f896ed2f78234f5e4dc5c303495 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 7 Dec 2021 12:32:11 -0800 Subject: [PATCH] feat(cli): add locator methods to the cli api (#10746) --- docs/src/cli.md | 12 ++++ .../server/supplements/injected/consoleApi.ts | 29 +++++++++ tests/inspector/console-api.spec.ts | 59 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 tests/inspector/console-api.spec.ts diff --git a/docs/src/cli.md b/docs/src/cli.md index f5ec29faec..b530643d90 100644 --- a/docs/src/cli.md +++ b/docs/src/cli.md @@ -427,6 +427,18 @@ Reveal element in the Elements panel (if DevTools of the respective browser supp > playwright.inspect('text=Log in') ``` +#### playwright.locator(selector) + +Query Playwright element using the actual Playwright query engine, for example: + +```js +> playwright.locator('.auth-form').withText('Log in'); + +> Locator () +> - element: button +> - elements: [button] +``` + #### playwright.selector(element) Generates selector for the given element. diff --git a/packages/playwright-core/src/server/supplements/injected/consoleApi.ts b/packages/playwright-core/src/server/supplements/injected/consoleApi.ts index 9d7b947db1..64589b4bcc 100644 --- a/packages/playwright-core/src/server/supplements/injected/consoleApi.ts +++ b/packages/playwright-core/src/server/supplements/injected/consoleApi.ts @@ -14,12 +14,40 @@ * limitations under the License. */ +import { escapeWithQuotes } from '../../../utils/stringUtils'; import type InjectedScript from '../../injected/injectedScript'; import { generateSelector } from '../../injected/selectorGenerator'; +function createLocator(injectedScript: InjectedScript, initial: string) { + class Locator { + selector: string; + element: Element | undefined; + elements: Element[]; + + constructor(selector: string) { + this.selector = selector; + const parsed = injectedScript.parseSelector(this.selector); + this.element = injectedScript.querySelector(parsed, document, false); + this.elements = injectedScript.querySelectorAll(parsed, document); + } + + locator(selector: string): Locator { + return new Locator(this.selector ? this.selector + ' >> ' + selector : selector); + } + + withText(text: string | RegExp): Locator { + const matcher = text instanceof RegExp ? 'text-matches' : 'has-text'; + const source = escapeWithQuotes(text instanceof RegExp ? text.source : text, '"'); + return new Locator(this.selector + ` >> :scope:${matcher}(${source})`); + } + } + return new Locator(initial); +} + type ConsoleAPIInterface = { $: (selector: string) => void; $$: (selector: string) => void; + locator: (selector: string) => any; inspect: (selector: string) => void; selector: (element: Element) => void; resume: () => void; @@ -43,6 +71,7 @@ export class ConsoleAPI { window.playwright = { $: (selector: string, strict?: boolean) => this._querySelector(selector, !!strict), $$: (selector: string) => this._querySelectorAll(selector), + locator: (selector: string) => createLocator(this._injectedScript, selector), inspect: (selector: string) => this._inspect(selector), selector: (element: Element) => this._selector(element), resume: () => this._resume(), diff --git a/tests/inspector/console-api.spec.ts b/tests/inspector/console-api.spec.ts new file mode 100644 index 0000000000..02148a43de --- /dev/null +++ b/tests/inspector/console-api.spec.ts @@ -0,0 +1,59 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { test as it, expect } from './inspectorTest'; + +it.skip(({ mode }) => mode !== 'default'); + +let scriptPromise; + +it.beforeEach(async ({ page, recorderPageGetter }) => { + scriptPromise = (async () => { + await page.pause(); + })(); + await recorderPageGetter(); +}); + +it.afterEach(async ({ recorderPageGetter }) => { + const recorderPage = await recorderPageGetter(); + recorderPage.click('[title=Resume]').catch(() => {}); + await scriptPromise; + recorderPage.click('[title=Resume]').catch(() => {}); +}); + +it('should support playwright.$, playwright.$$', async ({ page }) => { + const body = await page.evaluateHandle('playwright.$("body")'); + expect(body.toString()).toBe('JSHandle@node'); + const length = await page.evaluate('playwright.$$("body").length'); + expect(length).toBe(1); +}); + +it('should support playwright.selector', async ({ page }) => { + const length = await page.evaluate('playwright.selector(document.body)'); + expect(length).toBe('body'); +}); + +it('should support playwright.locator.value', async ({ page }) => { + await page.setContent('
Hello
'); + const handle = await page.evaluateHandle(`playwright.locator('div').withText('Hello').element`); + expect(handle.toString()).toBe('JSHandle@node'); +}); + +it('should support playwright.locator.values', async ({ page }) => { + await page.setContent('
Hello
'); + const length = await page.evaluate(`playwright.locator('div').withText('Hello').elements.length`); + expect(length).toBe(1); +});