chore: make input actions not use rerunnable task (#19638)
This commit is contained in:
parent
d5881b8d48
commit
cce29215f9
|
|
@ -1134,19 +1134,51 @@ export class Frame extends SdkObject {
|
||||||
return false;
|
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>(
|
private async _retryWithProgressIfNotConnected<R>(
|
||||||
progress: Progress,
|
progress: Progress,
|
||||||
selector: string,
|
selector: string,
|
||||||
strict: boolean | undefined,
|
strict: boolean | undefined,
|
||||||
action: (handle: dom.ElementHandle<Element>) => Promise<R | 'error:notconnected'>): Promise<R> {
|
action: (handle: dom.ElementHandle<Element>) => Promise<R | 'error:notconnected'>): Promise<R> {
|
||||||
return this.retryWithProgress(progress, selector, { strict }, async (selectorInFrame, continuePolling) => {
|
progress.log(`waiting for ${this._asLocator(selector)}`);
|
||||||
// We did not pass omitAttached, so selectorInFrame is not null.
|
return this.retryWithProgressAndTimeouts(progress, [0, 20, 50, 100, 100, 500], async continuePolling => {
|
||||||
const { frame, info } = selectorInFrame!;
|
const resolved = await this._resolveInjectedForSelector(progress, selector, strict);
|
||||||
// Be careful, |this| can be different from |frame|.
|
if (!resolved)
|
||||||
const task = dom.waitForSelectorTask(info, 'attached');
|
return continuePolling;
|
||||||
progress.log(`waiting for ${this._asLocator(selector)}`);
|
const result = await resolved.injected.evaluateHandle((injected, { info }) => {
|
||||||
const handle = await frame._scheduleRerunnableHandleTask(progress, info.world, task);
|
const elements = injected.querySelectorAll(info.parsed, document);
|
||||||
const element = handle.asElement() as dom.ElementHandle<Element>;
|
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 {
|
try {
|
||||||
const result = await action(element);
|
const result = await action(element);
|
||||||
if (result === 'error:notconnected') {
|
if (result === 'error:notconnected') {
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ it('should throw on unsupported inputs', async ({ page, server }) => {
|
||||||
await page.$eval('input', (input, type) => input.setAttribute('type', type), type);
|
await page.$eval('input', (input, type) => input.setAttribute('type', type), type);
|
||||||
let error = null;
|
let error = null;
|
||||||
await page.fill('input', '').catch(e => error = e);
|
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`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 }) => {
|
it('should report logs when the selector resolves to multiple elements', async ({ page, server }) => {
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
await page.setContent(`
|
await page.setContent(`
|
||||||
<button style="display: none; position: absolute; top: 0px; left: 0px; width: 100%;">
|
<button style="display: none; position: absolute; top: 0px; left: 0px; width: 100%;">Reset</button>
|
||||||
Reset
|
<button>Reset</button>
|
||||||
</button>
|
|
||||||
<button>
|
|
||||||
Reset
|
|
||||||
</button>
|
|
||||||
`);
|
`);
|
||||||
const error = await page.click('text=Reset', {
|
const error = await page.click('text=Reset', {
|
||||||
timeout: 1000
|
timeout: 1000
|
||||||
}).catch(e => e);
|
}).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 }) => {
|
it('should resolve promise when node is added in shadow dom', async ({ page, server }) => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue