mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: move some injected code to InjectedScript (#19609)
This commit is contained in:
parent
0844394270
commit
ae2b1ac5e8
@ -1386,37 +1386,8 @@ export class Frame extends SdkObject {
|
||||
if (oneShot)
|
||||
outerProgress.log(`${metadata.apiName}${timeout ? ` with timeout ${timeout}ms` : ''}`);
|
||||
return await this._scheduleRerunnableTaskWithProgress(outerProgress, selector, (progress, element, options, elements) => {
|
||||
let result: { matches: boolean, received?: any };
|
||||
|
||||
if (options.isArray) {
|
||||
result = progress.injectedScript.expectArray(elements, options);
|
||||
} else {
|
||||
if (!element) {
|
||||
// expect(locator).toBeHidden() passes when there is no element.
|
||||
if (!options.isNot && options.expression === 'to.be.hidden')
|
||||
return { matches: true };
|
||||
// expect(locator).not.toBeVisible() passes when there is no element.
|
||||
if (options.isNot && options.expression === 'to.be.visible')
|
||||
return { matches: false };
|
||||
// When none of the above applies, keep waiting for the element.
|
||||
return options.oneShot ? { matches: options.isNot } : progress.continuePolling;
|
||||
}
|
||||
result = progress.injectedScript.expectSingleElement(progress, element, options);
|
||||
}
|
||||
|
||||
if (result.matches === options.isNot) {
|
||||
// Keep waiting in these cases:
|
||||
// expect(locator).conditionThatDoesNotMatch
|
||||
// expect(locator).not.conditionThatDoesMatch
|
||||
progress.setIntermediateResult(result.received);
|
||||
if (!Array.isArray(result.received))
|
||||
progress.log(` unexpected value "${progress.injectedScript.renderUnexpectedValue(options.expression, result.received)}"`);
|
||||
return options.oneShot ? result : progress.continuePolling;
|
||||
}
|
||||
|
||||
// Reached the expected state!
|
||||
return result;
|
||||
}, { ...options, isArray, oneShot }, { strict: true, querySelectorAll: isArray, mainWorld, omitAttached, logScale: true, ...options });
|
||||
return progress.injectedScript.expect(progress, element, options, elements);
|
||||
}, { ...options, oneShot }, { strict: true, querySelectorAll: isArray, mainWorld, omitAttached, logScale: true, ...options });
|
||||
}, oneShot ? 0 : timeout).catch(e => {
|
||||
// Q: Why not throw upon isSessionClosedError(e) as in other places?
|
||||
// A: We want user to receive a friendly message containing the last intermediate result.
|
||||
@ -1545,7 +1516,6 @@ export class Frame extends SdkObject {
|
||||
return injectedScript.evaluateHandle((injected, { info, taskData, callbackText, querySelectorAll, logScale, omitAttached, snapshotName }) => {
|
||||
const callback = injected.eval(callbackText) as DomTaskBody<T, R, Element | undefined>;
|
||||
const poller = logScale ? injected.pollLogScale.bind(injected) : injected.pollRaf.bind(injected);
|
||||
let markedElements = new Set<Element>();
|
||||
return poller(progress => {
|
||||
let element: Element | undefined;
|
||||
let elements: Element[] = [];
|
||||
@ -1563,19 +1533,8 @@ export class Frame extends SdkObject {
|
||||
if (!element && !omitAttached)
|
||||
return progress.continuePolling;
|
||||
|
||||
if (snapshotName) {
|
||||
const previouslyMarkedElements = markedElements;
|
||||
markedElements = new Set(elements);
|
||||
for (const e of previouslyMarkedElements) {
|
||||
if (!markedElements.has(e))
|
||||
e.removeAttribute('__playwright_target__');
|
||||
}
|
||||
for (const e of markedElements) {
|
||||
if (!previouslyMarkedElements.has(e))
|
||||
e.setAttribute('__playwright_target__', snapshotName);
|
||||
}
|
||||
}
|
||||
|
||||
if (snapshotName)
|
||||
injected.markTargetElements(new Set(elements), snapshotName);
|
||||
return callback(progress, element, taskData as T, elements);
|
||||
});
|
||||
}, { info, taskData, callbackText, querySelectorAll: options.querySelectorAll, logScale: options.logScale, omitAttached: options.omitAttached, snapshotName: progress.metadata.afterSnapshot });
|
||||
|
||||
@ -80,6 +80,7 @@ export class InjectedScript {
|
||||
readonly isUnderTest: boolean;
|
||||
private _sdkLanguage: Language;
|
||||
private _testIdAttributeNameForStrictErrorAndConsoleCodegen: string = 'data-testid';
|
||||
private _markedTargetElements = new Set<Element>();
|
||||
|
||||
constructor(isUnderTest: boolean, sdkLanguage: Language, testIdAttributeNameForStrictErrorAndConsoleCodegen: string, stableRafCount: number, browserName: string, customEngines: { name: string, engine: SelectorEngine }[]) {
|
||||
this.isUnderTest = isUnderTest;
|
||||
@ -1070,6 +1071,18 @@ export class InjectedScript {
|
||||
}
|
||||
}
|
||||
|
||||
markTargetElements(markedElements: Set<Element>, snapshotName: string) {
|
||||
for (const e of this._markedTargetElements) {
|
||||
if (!markedElements.has(e))
|
||||
e.removeAttribute('__playwright_target__');
|
||||
}
|
||||
for (const e of markedElements) {
|
||||
if (!this._markedTargetElements.has(e))
|
||||
e.setAttribute('__playwright_target__', snapshotName);
|
||||
}
|
||||
this._markedTargetElements = markedElements;
|
||||
}
|
||||
|
||||
private _setupGlobalListenersRemovalDetection() {
|
||||
const customEventName = '__playwright_global_listeners_check__';
|
||||
|
||||
@ -1105,43 +1118,77 @@ export class InjectedScript {
|
||||
this.onGlobalListenersRemoved.add(addHitTargetInterceptorListeners);
|
||||
}
|
||||
|
||||
expectSingleElement(progress: InjectedScriptProgress, element: Element, options: FrameExpectParams): { matches: boolean, received?: any } {
|
||||
const injected = progress.injectedScript;
|
||||
expect(progress: InjectedScriptProgress, element: Element | undefined, options: FrameExpectParams & { oneShot: boolean }, elements: Element[]) {
|
||||
const isArray = options.expression === 'to.have.count' || options.expression.endsWith('.array');
|
||||
|
||||
let result: { matches: boolean, received?: any };
|
||||
|
||||
if (isArray) {
|
||||
result = this.expectArray(elements, options);
|
||||
} else {
|
||||
if (!element) {
|
||||
// expect(locator).toBeHidden() passes when there is no element.
|
||||
if (!options.isNot && options.expression === 'to.be.hidden')
|
||||
return { matches: true };
|
||||
// expect(locator).not.toBeVisible() passes when there is no element.
|
||||
if (options.isNot && options.expression === 'to.be.visible')
|
||||
return { matches: false };
|
||||
// When none of the above applies, keep waiting for the element.
|
||||
return options.oneShot ? { matches: options.isNot } : progress.continuePolling;
|
||||
}
|
||||
result = this.expectSingleElement(element, options);
|
||||
}
|
||||
|
||||
if (result.matches === options.isNot) {
|
||||
// Keep waiting in these cases:
|
||||
// expect(locator).conditionThatDoesNotMatch
|
||||
// expect(locator).not.conditionThatDoesMatch
|
||||
progress.setIntermediateResult(result.received);
|
||||
if (!Array.isArray(result.received))
|
||||
progress.log(` unexpected value "${this.renderUnexpectedValue(options.expression, result.received)}"`);
|
||||
return options.oneShot ? result : progress.continuePolling;
|
||||
}
|
||||
|
||||
// Reached the expected state!
|
||||
return result;
|
||||
}
|
||||
|
||||
private expectSingleElement(element: Element, options: FrameExpectParams): { matches: boolean, received?: any } {
|
||||
const expression = options.expression;
|
||||
|
||||
{
|
||||
// Element state / boolean values.
|
||||
let elementState: boolean | 'error:notconnected' | 'error:notcheckbox' | undefined;
|
||||
if (expression === 'to.be.checked') {
|
||||
elementState = progress.injectedScript.elementState(element, 'checked');
|
||||
elementState = this.elementState(element, 'checked');
|
||||
} else if (expression === 'to.be.unchecked') {
|
||||
elementState = progress.injectedScript.elementState(element, 'unchecked');
|
||||
elementState = this.elementState(element, 'unchecked');
|
||||
} else if (expression === 'to.be.disabled') {
|
||||
elementState = progress.injectedScript.elementState(element, 'disabled');
|
||||
elementState = this.elementState(element, 'disabled');
|
||||
} else if (expression === 'to.be.editable') {
|
||||
elementState = progress.injectedScript.elementState(element, 'editable');
|
||||
elementState = this.elementState(element, 'editable');
|
||||
} else if (expression === 'to.be.readonly') {
|
||||
elementState = !progress.injectedScript.elementState(element, 'editable');
|
||||
elementState = !this.elementState(element, 'editable');
|
||||
} else if (expression === 'to.be.empty') {
|
||||
if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA')
|
||||
elementState = !(element as HTMLInputElement).value;
|
||||
else
|
||||
elementState = !element.textContent?.trim();
|
||||
} else if (expression === 'to.be.enabled') {
|
||||
elementState = progress.injectedScript.elementState(element, 'enabled');
|
||||
elementState = this.elementState(element, 'enabled');
|
||||
} else if (expression === 'to.be.focused') {
|
||||
elementState = this._activelyFocused(element).isFocused;
|
||||
} else if (expression === 'to.be.hidden') {
|
||||
elementState = progress.injectedScript.elementState(element, 'hidden');
|
||||
elementState = this.elementState(element, 'hidden');
|
||||
} else if (expression === 'to.be.visible') {
|
||||
elementState = progress.injectedScript.elementState(element, 'visible');
|
||||
elementState = this.elementState(element, 'visible');
|
||||
}
|
||||
|
||||
if (elementState !== undefined) {
|
||||
if (elementState === 'error:notcheckbox')
|
||||
throw injected.createStacklessError('Element is not a checkbox');
|
||||
throw this.createStacklessError('Element is not a checkbox');
|
||||
if (elementState === 'error:notconnected')
|
||||
throw injected.createStacklessError('Element is not connected');
|
||||
throw this.createStacklessError('Element is not connected');
|
||||
return { received: elementState, matches: elementState };
|
||||
}
|
||||
}
|
||||
@ -1205,7 +1252,7 @@ export class InjectedScript {
|
||||
throw this.createStacklessError('Unknown expect matcher: ' + expression);
|
||||
}
|
||||
|
||||
renderUnexpectedValue(expression: string, received: any): string {
|
||||
private renderUnexpectedValue(expression: string, received: any): string {
|
||||
if (expression === 'to.be.checked')
|
||||
return received ? 'checked' : 'unchecked';
|
||||
if (expression === 'to.be.unchecked')
|
||||
@ -1229,7 +1276,7 @@ export class InjectedScript {
|
||||
return received;
|
||||
}
|
||||
|
||||
expectArray(elements: Element[], options: FrameExpectParams): { matches: boolean, received?: any } {
|
||||
private expectArray(elements: Element[], options: FrameExpectParams): { matches: boolean, received?: any } {
|
||||
const expression = options.expression;
|
||||
|
||||
if (expression === 'to.have.count') {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user