mirror of
https://github.com/microsoft/playwright.git
synced 2024-09-11 20:37:54 +03:00
fix(expect): continue polling in array-based matchers, fix edge cases (#9430)
This commit is contained in:
parent
b1160ec239
commit
876e08315b
@ -1163,10 +1163,11 @@ export class Frame extends SdkObject {
|
||||
|
||||
async expect(metadata: CallMetadata, selector: string, options: FrameExpectParams): Promise<{ pass: boolean, received?: any, log?: string[] }> {
|
||||
const controller = new ProgressController(metadata, this);
|
||||
const querySelectorAll = options.expression === 'to.have.count' || options.expression.endsWith('.array');
|
||||
const isListMatcher = options.expression.endsWith('.array');
|
||||
const querySelectorAll = options.expression === 'to.have.count' || isListMatcher;
|
||||
const mainWorld = options.expression === 'to.have.property';
|
||||
const omitAttached = (!options.isNot && options.expression === 'to.be.hidden') || (options.isNot && options.expression === 'to.be.visible');
|
||||
|
||||
const expectsEmptyList = options.expectedText?.length === 0;
|
||||
const omitAttached = (isListMatcher && options.isNot !== expectsEmptyList) || (!options.isNot && options.expression === 'to.be.hidden') || (options.isNot && options.expression === 'to.be.visible');
|
||||
return await this._scheduleRerunnableTaskWithController(controller, selector, (progress, element, options, elements, continuePolling) => {
|
||||
// We don't have an element and we don't need an element => pass.
|
||||
if (!element && options.omitAttached)
|
||||
@ -1263,18 +1264,22 @@ export class Frame extends SdkObject {
|
||||
const callback = injected.eval(callbackText) as DomTaskBody<T, R, Element | undefined>;
|
||||
const poller = logScale ? injected.pollLogScale.bind(injected) : injected.pollRaf.bind(injected);
|
||||
return poller((progress, continuePolling) => {
|
||||
let element: Element | undefined;
|
||||
let elements: Element[] = [];
|
||||
if (querySelectorAll) {
|
||||
const elements = injected.querySelectorAll(info.parsed, document);
|
||||
elements = injected.querySelectorAll(info.parsed, document);
|
||||
element = elements[0];
|
||||
progress.logRepeating(` selector resolved to ${elements.length} element${elements.length === 1 ? '' : 's'}`);
|
||||
return callback(progress, elements[0], taskData as T, elements, continuePolling);
|
||||
} else {
|
||||
element = injected.querySelector(info.parsed, document, info.strict);
|
||||
elements = [];
|
||||
if (element)
|
||||
progress.logRepeating(` selector resolved to ${injected.previewNode(element)}`);
|
||||
}
|
||||
|
||||
const element = injected.querySelector(info.parsed, document, info.strict);
|
||||
if (!element && !omitAttached)
|
||||
return continuePolling;
|
||||
if (element)
|
||||
progress.logRepeating(` selector resolved to ${injected.previewNode(element)}`);
|
||||
return callback(progress, element, taskData as T, [], continuePolling);
|
||||
return callback(progress, element, taskData as T, elements, continuePolling);
|
||||
});
|
||||
}, { info, taskData, callbackText, querySelectorAll: options.querySelectorAll, logScale: options.logScale, omitAttached: options.omitAttached });
|
||||
}, true);
|
||||
|
@ -890,22 +890,30 @@ export class InjectedScript {
|
||||
if (received && options.expectedText) {
|
||||
// "To match an array" is "to contain an array" + "equal length"
|
||||
const lengthShouldMatch = expression !== 'to.contain.text.array';
|
||||
if (received.length !== options.expectedText.length && lengthShouldMatch) {
|
||||
const matchesLength = received.length === options.expectedText.length || !lengthShouldMatch;
|
||||
if (matchesLength === options.isNot) {
|
||||
progress.setIntermediateResult(received);
|
||||
return continuePolling;
|
||||
}
|
||||
if (!matchesLength)
|
||||
return { received, pass: !options.isNot };
|
||||
|
||||
// Each matcher should get a "received" that matches it, in order.
|
||||
let i = 0;
|
||||
const matchers = options.expectedText.map(e => new ExpectedTextMatcher(e));
|
||||
let allMatchesFound = true;
|
||||
for (const matcher of matchers) {
|
||||
while (i < received.length && matcher.matches(received[i]) === options.isNot)
|
||||
while (i < received.length && !matcher.matches(received[i]))
|
||||
i++;
|
||||
if (i === received.length) {
|
||||
progress.setIntermediateResult(received);
|
||||
return continuePolling;
|
||||
if (i >= received.length) {
|
||||
allMatchesFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allMatchesFound === options.isNot) {
|
||||
progress.setIntermediateResult(received);
|
||||
return continuePolling;
|
||||
}
|
||||
return { received, pass: !options.isNot };
|
||||
}
|
||||
}
|
||||
|
@ -155,6 +155,44 @@ test('should support toHaveText w/ array', async ({ runInlineTest }) => {
|
||||
await expect(locator).toHaveText(['Text 1', /Text \\d+a/]);
|
||||
});
|
||||
|
||||
test('pass lazy', async ({ page }) => {
|
||||
await page.setContent('<div id=div></div>');
|
||||
const locator = page.locator('p');
|
||||
setTimeout(() => {
|
||||
page.evaluate(() => {
|
||||
div.innerHTML = "<p>Text 1</p><p>Text 2</p>";
|
||||
}).catch(() => {});
|
||||
}, 500);
|
||||
await expect(locator).toHaveText(['Text 1', 'Text 2']);
|
||||
});
|
||||
|
||||
test('pass empty', async ({ page }) => {
|
||||
await page.setContent('<div></div>');
|
||||
const locator = page.locator('p');
|
||||
await expect(locator).toHaveText([]);
|
||||
});
|
||||
|
||||
test('pass not empty', async ({ page }) => {
|
||||
await page.setContent('<div><p>Test</p></div>');
|
||||
const locator = page.locator('p');
|
||||
await expect(locator).not.toHaveText([]);
|
||||
});
|
||||
|
||||
test('pass on empty', async ({ page }) => {
|
||||
await page.setContent('<div></div>');
|
||||
const locator = page.locator('p');
|
||||
await expect(locator).not.toHaveText(['Test']);
|
||||
});
|
||||
|
||||
test('pass eventually empty', async ({ page }) => {
|
||||
await page.setContent('<div id=div><p>Text</p></div>');
|
||||
const locator = page.locator('p');
|
||||
setTimeout(() => {
|
||||
page.evaluate(() => div.innerHTML = "").catch(() => {});
|
||||
}, 500);
|
||||
await expect(locator).not.toHaveText([]);
|
||||
});
|
||||
|
||||
test('fail', async ({ page }) => {
|
||||
await page.setContent('<div>Text 1</div><div>Text 3</div>');
|
||||
const locator = page.locator('div');
|
||||
@ -168,7 +206,7 @@ test('should support toHaveText w/ array', async ({ runInlineTest }) => {
|
||||
expect(output).toContain('- "Extra"');
|
||||
expect(output).toContain('waiting for selector "div"');
|
||||
expect(output).toContain('selector resolved to 2 elements');
|
||||
expect(result.passed).toBe(1);
|
||||
expect(result.passed).toBe(6);
|
||||
expect(result.failed).toBe(1);
|
||||
expect(result.exitCode).toBe(1);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user