Skip to content

Commit

Permalink
chore: make input actions not use rerunnable task (#19638)
Browse files Browse the repository at this point in the history
  • Loading branch information
dgozman committed Dec 24, 2022
1 parent d5881b8 commit cce2921
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 16 deletions.
48 changes: 40 additions & 8 deletions packages/playwright-core/src/server/frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1134,19 +1134,51 @@ export class Frame extends SdkObject {
return false;
}

private async _resolveInjectedForSelector(progress: Progress, selector: string, strict: boolean | undefined): Promise<{ injected: js.JSHandle<InjectedScript>, info: SelectorInfo } | undefined> {
const selectorInFrame = await this.resolveFrameForSelectorNoWait(selector, { strict });
if (!selectorInFrame)
return;
progress.throwIfAborted();

// Be careful, |this| can be different from |selectorInFrame.frame|.
const context = await selectorInFrame.frame._context(selectorInFrame.info.world);
const injected = await context.injectedScript();
progress.throwIfAborted();
return { injected, info: selectorInFrame.info };
}

private async _retryWithProgressIfNotConnected<R>(
progress: Progress,
selector: string,
strict: boolean | undefined,
action: (handle: dom.ElementHandle<Element>) => Promise<R | 'error:notconnected'>): Promise<R> {
return this.retryWithProgress(progress, selector, { strict }, async (selectorInFrame, continuePolling) => {
// We did not pass omitAttached, so selectorInFrame is not null.
const { frame, info } = selectorInFrame!;
// Be careful, |this| can be different from |frame|.
const task = dom.waitForSelectorTask(info, 'attached');
progress.log(`waiting for ${this._asLocator(selector)}`);
const handle = await frame._scheduleRerunnableHandleTask(progress, info.world, task);
const element = handle.asElement() as dom.ElementHandle<Element>;
progress.log(`waiting for ${this._asLocator(selector)}`);
return this.retryWithProgressAndTimeouts(progress, [0, 20, 50, 100, 100, 500], async continuePolling => {
const resolved = await this._resolveInjectedForSelector(progress, selector, strict);
if (!resolved)
return continuePolling;
const result = await resolved.injected.evaluateHandle((injected, { info }) => {
const elements = injected.querySelectorAll(info.parsed, document);
const element = elements[0] as Element | undefined;
let log = '';
if (elements.length > 1) {
if (info.strict)
throw injected.strictModeViolationError(info.parsed, elements);
log = ` locator resolved to ${elements.length} elements. Proceeding with the first one: ${injected.previewNode(elements[0])}`;
} else if (element) {
log = ` locator resolved to ${injected.previewNode(element)}`;
}
return { log, success: !!element, element };
}, { info: resolved.info });
const { log, success } = await result.evaluate(r => ({ log: r.log, success: r.success }));
if (log)
progress.log(log);
if (!success) {
result.dispose();
return continuePolling;
}
const element = await result.evaluateHandle(r => r.element) as dom.ElementHandle<Element>;
result.dispose();
try {
const result = await action(element);
if (result === 'error:notconnected') {
Expand Down
2 changes: 1 addition & 1 deletion tests/page/page-fill.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ it('should throw on unsupported inputs', async ({ page, server }) => {
await page.$eval('input', (input, type) => input.setAttribute('type', type), type);
let error = null;
await page.fill('input', '').catch(e => error = e);
expect(error.message).toContain(`input of type "${type}" cannot be filled`);
expect(error.message).toContain(`Input of type "${type}" cannot be filled`);
}
});

Expand Down
10 changes: 3 additions & 7 deletions tests/page/page-wait-for-selector-1.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,17 +172,13 @@ it('should report logs while waiting for hidden', async ({ page, server }) => {
it('should report logs when the selector resolves to multiple elements', async ({ page, server }) => {
await page.goto(server.EMPTY_PAGE);
await page.setContent(`
<button style="display: none; position: absolute; top: 0px; left: 0px; width: 100%;">
Reset
</button>
<button>
Reset
</button>
<button style="display: none; position: absolute; top: 0px; left: 0px; width: 100%;">Reset</button>
<button>Reset</button>
`);
const error = await page.click('text=Reset', {
timeout: 1000
}).catch(e => e);
expect(error.toString()).toContain('locator resolved to 2 elements. Proceeding with the first one.');
expect(error.toString()).toContain('locator resolved to 2 elements. Proceeding with the first one: <button>Reset</button>');
});

it('should resolve promise when node is added in shadow dom', async ({ page, server }) => {
Expand Down

0 comments on commit cce2921

Please sign in to comment.