From 5a162c5840bc84f9e641f2e04e0cc65507f28f9b Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Mon, 21 Oct 2024 17:11:15 +0200 Subject: [PATCH] implement for JS and python --- .../src/utils/isomorphic/locatorGenerators.ts | 22 +++++++++++++++++-- tests/library/locator-generator.spec.ts | 8 +++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/utils/isomorphic/locatorGenerators.ts b/packages/playwright-core/src/utils/isomorphic/locatorGenerators.ts index 7cb658340c..ecc0d8f47f 100644 --- a/packages/playwright-core/src/utils/isomorphic/locatorGenerators.ts +++ b/packages/playwright-core/src/utils/isomorphic/locatorGenerators.ts @@ -19,7 +19,7 @@ import { type NestedSelectorBody, parseAttributeSelector, parseSelector, stringi import type { ParsedSelector } from './selectorParser'; export type Language = 'javascript' | 'python' | 'java' | 'csharp' | 'jsonl'; -export type LocatorType = 'default' | 'role' | 'text' | 'label' | 'placeholder' | 'alt' | 'title' | 'test-id' | 'nth' | 'first' | 'last' | 'has-text' | 'has-not-text' | 'has' | 'hasNot' | 'frame' | 'and' | 'or' | 'chain'; +export type LocatorType = 'default' | 'role' | 'text' | 'label' | 'placeholder' | 'alt' | 'title' | 'test-id' | 'nth' | 'first' | 'last' | 'has-text' | 'has-not-text' | 'has' | 'hasNot' | 'frame' | 'frame-locator' | 'and' | 'or' | 'chain'; export type LocatorBase = 'page' | 'locator' | 'frame-locator'; export type Quote = '\'' | '"' | '`'; @@ -194,8 +194,22 @@ function innerAsLocators(factory: LocatorFactory, parsed: ParsedSelector, isFram const selectorPart = stringifySelector({ parts: [part] }, /* forceEngineName */ true); locatorPartWithEngine = factory.generateLocator(base, 'default', selectorPart); } + const locatorParts = [locatorPart, locatorPartWithEngine].filter(Boolean) as string[]; - tokens.push([locatorPart, locatorPartWithEngine].filter(Boolean) as string[]); + if (nextPart && nextPart.name === 'internal:control' && (nextPart.body as string) === 'enter-frame') { + // Two options: + // - locator('iframe').contentFrame() + // - frameLocator('iframe') + tokens.push([ + ...locatorParts.map(p => factory.chainLocators([p, factory.generateLocator(base, 'frame', '')])), + factory.generateLocator(base, 'frame-locator', selectorPart), + ]); + nextBase = 'frame-locator'; + index++; + continue; + } + + tokens.push(locatorParts); } return combineTokens(factory, tokens, maxOutputSize); @@ -251,6 +265,8 @@ export class JavaScriptLocatorFactory implements LocatorFactory { if (options.hasNotText !== undefined) return `locator(${this.quote(body as string)}, { hasNotText: ${this.toHasText(options.hasNotText)} })`; return `locator(${this.quote(body as string)})`; + case 'frame-locator': + return `frameLocator(${this.quote(body as string)})`; case 'frame': return `contentFrame()`; case 'nth': @@ -343,6 +359,8 @@ export class PythonLocatorFactory implements LocatorFactory { if (options.hasNotText !== undefined) return `locator(${this.quote(body as string)}, has_not_text=${this.toHasText(options.hasNotText)})`; return `locator(${this.quote(body as string)})`; + case 'frame-locator': + return `frame_locator(${this.quote(body as string)})`; case 'frame': return `content_frame`; case 'nth': diff --git a/tests/library/locator-generator.spec.ts b/tests/library/locator-generator.spec.ts index d177fa7489..90125c7547 100644 --- a/tests/library/locator-generator.spec.ts +++ b/tests/library/locator-generator.spec.ts @@ -584,3 +584,11 @@ it('parse locators strictly', () => { expect.soft(parseLocator('javascript', `locator('div').filter({ hasText: 'Goodbye world' }}).locator('span')`)).not.toBe(selector); expect.soft(parseLocator('python', `locator("div").filter(has_text=="Goodbye world").locator("span")`)).not.toBe(selector); }); + +it('parseLocator frames', async () => { + expect.soft(parseLocator('javascript', `locator('iframe').contentFrame().getByText('foo')`, '')).toBe(`iframe >> internal:control=enter-frame >> internal:text=\"foo\"i`); + expect.soft(parseLocator('javascript', `frameLocator('iframe').getByText('foo')`, '')).toBe(`iframe >> internal:control=enter-frame >> internal:text=\"foo\"i`); + + expect.soft(parseLocator('python', `locator("iframe").content_frame.get_by_text("foo")`, '')).toBe(`iframe >> internal:control=enter-frame >> internal:text=\"foo\"i`); + expect.soft(parseLocator('python', `frame_locator("iframe").get_by_text("foo")`, '')).toBe(`iframe >> internal:control=enter-frame >> internal:text=\"foo\"i`); +});