fix!: fix bounding box visibility conditions (#8954)
This commit is contained in:
parent
64763e973b
commit
ac9929d80f
@ -19,6 +19,8 @@ export const createFunction = (
|
|||||||
return fn;
|
return fn;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const HIDDEN_VISIBILITY_VALUES = ['hidden', 'collapse'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -38,11 +40,20 @@ export const checkVisibility = (
|
|||||||
|
|
||||||
const style = window.getComputedStyle(element);
|
const style = window.getComputedStyle(element);
|
||||||
const isVisible =
|
const isVisible =
|
||||||
style && style.visibility !== 'hidden' && isBoundingBoxVisible(element);
|
style &&
|
||||||
|
!HIDDEN_VISIBILITY_VALUES.includes(style.visibility) &&
|
||||||
|
isBoundingBoxVisible(element);
|
||||||
return visible === isVisible ? node : false;
|
return visible === isVisible ? node : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
function isBoundingBoxVisible(element: Element): boolean {
|
function isBoundingBoxVisible(element: Element): boolean {
|
||||||
const rect = element.getBoundingClientRect();
|
const rect = element.getBoundingClientRect();
|
||||||
return !!(rect.top || rect.bottom || rect.width || rect.height);
|
return (
|
||||||
|
rect.width > 0 &&
|
||||||
|
rect.height > 0 &&
|
||||||
|
rect.right > 0 &&
|
||||||
|
rect.bottom > 0 &&
|
||||||
|
rect.left < self.innerWidth &&
|
||||||
|
rect.top < self.innerHeight
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -446,7 +446,7 @@ describe('AriaQueryHandler', () => {
|
|||||||
|
|
||||||
let divHidden = false;
|
let divHidden = false;
|
||||||
await page.setContent(
|
await page.setContent(
|
||||||
`<div role='button' style='display: block;'></div>`
|
`<div role='button' style='display: block;'>text</div>`
|
||||||
);
|
);
|
||||||
const waitForSelector = page
|
const waitForSelector = page
|
||||||
.waitForSelector('aria/[role="button"]', {hidden: true})
|
.waitForSelector('aria/[role="button"]', {hidden: true})
|
||||||
@ -468,7 +468,9 @@ describe('AriaQueryHandler', () => {
|
|||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
|
|
||||||
let divHidden = false;
|
let divHidden = false;
|
||||||
await page.setContent(`<div role='main' style='display: block;'></div>`);
|
await page.setContent(
|
||||||
|
`<div role='main' style='display: block;'>text</div>`
|
||||||
|
);
|
||||||
const waitForSelector = page
|
const waitForSelector = page
|
||||||
.waitForSelector('aria/[role="main"]', {hidden: true})
|
.waitForSelector('aria/[role="main"]', {hidden: true})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -488,7 +490,7 @@ describe('AriaQueryHandler', () => {
|
|||||||
it('hidden should wait for removal', async () => {
|
it('hidden should wait for removal', async () => {
|
||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
|
|
||||||
await page.setContent(`<div role='main'></div>`);
|
await page.setContent(`<div role='main'>text</div>`);
|
||||||
let divRemoved = false;
|
let divRemoved = false;
|
||||||
const waitForSelector = page
|
const waitForSelector = page
|
||||||
.waitForSelector('aria/[role="main"]', {hidden: true})
|
.waitForSelector('aria/[role="main"]', {hidden: true})
|
||||||
@ -516,13 +518,13 @@ describe('AriaQueryHandler', () => {
|
|||||||
it('should respect timeout', async () => {
|
it('should respect timeout', async () => {
|
||||||
const {page, puppeteer} = getTestState();
|
const {page, puppeteer} = getTestState();
|
||||||
|
|
||||||
let error!: Error;
|
const error = await page
|
||||||
await page
|
.waitForSelector('aria/[role="button"]', {
|
||||||
.waitForSelector('aria/[role="button"]', {timeout: 10})
|
timeout: 10,
|
||||||
.catch(error_ => {
|
})
|
||||||
return (error = error_);
|
.catch(error => {
|
||||||
|
return error;
|
||||||
});
|
});
|
||||||
expect(error).toBeTruthy();
|
|
||||||
expect(error.message).toContain(
|
expect(error.message).toContain(
|
||||||
'Waiting for selector `[role="button"]` failed: Waiting failed: 10ms exceeded'
|
'Waiting for selector `[role="button"]` failed: Waiting failed: 10ms exceeded'
|
||||||
);
|
);
|
||||||
@ -532,17 +534,15 @@ describe('AriaQueryHandler', () => {
|
|||||||
it('should have an error message specifically for awaiting an element to be hidden', async () => {
|
it('should have an error message specifically for awaiting an element to be hidden', async () => {
|
||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
|
|
||||||
await page.setContent(`<div role='main'></div>`);
|
await page.setContent(`<div role='main'>text</div>`);
|
||||||
let error!: Error;
|
const promise = page.waitForSelector('aria/[role="main"]', {
|
||||||
await page
|
hidden: true,
|
||||||
.waitForSelector('aria/[role="main"]', {hidden: true, timeout: 10})
|
timeout: 10,
|
||||||
.catch(error_ => {
|
});
|
||||||
return (error = error_);
|
await expect(promise).rejects.toMatchObject({
|
||||||
});
|
message:
|
||||||
expect(error).toBeTruthy();
|
'Waiting for selector `[role="main"]` failed: Waiting failed: 10ms exceeded',
|
||||||
expect(error.message).toContain(
|
});
|
||||||
'Waiting for selector `[role="main"]` failed: Waiting failed: 10ms exceeded'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should respond to node attribute mutation', async () => {
|
it('should respond to node attribute mutation', async () => {
|
||||||
|
@ -295,3 +295,14 @@ export const shortWaitForArrayToHaveAtLeastNElements = async (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createTimeout = <T>(
|
||||||
|
n: number,
|
||||||
|
value?: T
|
||||||
|
): Promise<T | undefined> => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
return resolve(value);
|
||||||
|
}, n);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
import expect from 'expect';
|
import expect from 'expect';
|
||||||
import {isErrorLike} from '../../lib/cjs/puppeteer/util/ErrorLike.js';
|
import {isErrorLike} from '../../lib/cjs/puppeteer/util/ErrorLike.js';
|
||||||
import {
|
import {
|
||||||
|
createTimeout,
|
||||||
getTestState,
|
getTestState,
|
||||||
setupTestBrowserHooks,
|
setupTestBrowserHooks,
|
||||||
setupTestPageAndContextHooks,
|
setupTestPageAndContextHooks,
|
||||||
@ -478,113 +479,186 @@ describe('waittask specs', function () {
|
|||||||
await waitForSelector;
|
await waitForSelector;
|
||||||
expect(boxFound).toBe(true);
|
expect(boxFound).toBe(true);
|
||||||
});
|
});
|
||||||
it('should wait for visible', async () => {
|
it('should wait for element to be visible (display)', async () => {
|
||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
|
|
||||||
let divFound = false;
|
const promise = page.waitForSelector('div', {visible: true});
|
||||||
const waitForSelector = page
|
await page.setContent('<div style="display: none">text</div>');
|
||||||
.waitForSelector('div', {visible: true})
|
const element = await page.evaluateHandle(() => {
|
||||||
.then(() => {
|
return document.getElementsByTagName('div')[0]!;
|
||||||
return (divFound = true);
|
|
||||||
});
|
|
||||||
await page.setContent(
|
|
||||||
`<div style='display: none; visibility: hidden;'>1</div>`
|
|
||||||
);
|
|
||||||
expect(divFound).toBe(false);
|
|
||||||
await page.evaluate(() => {
|
|
||||||
return document.querySelector('div')?.style.removeProperty('display');
|
|
||||||
});
|
});
|
||||||
expect(divFound).toBe(false);
|
await expect(
|
||||||
await page.evaluate(() => {
|
Promise.race([promise, createTimeout(40)])
|
||||||
return document
|
).resolves.toBeFalsy();
|
||||||
.querySelector('div')
|
await element.evaluate(e => {
|
||||||
?.style.removeProperty('visibility');
|
e.style.removeProperty('display');
|
||||||
});
|
});
|
||||||
expect(await waitForSelector).toBe(true);
|
await expect(promise).resolves.toBeTruthy();
|
||||||
expect(divFound).toBe(true);
|
|
||||||
});
|
});
|
||||||
it('should wait for visible recursively', async () => {
|
it('should wait for element to be visible (visibility)', async () => {
|
||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
|
|
||||||
let divVisible = false;
|
const promise = page.waitForSelector('div', {visible: true});
|
||||||
const waitForSelector = page
|
await page.setContent('<div style="visibility: hidden">text</div>');
|
||||||
.waitForSelector('div#inner', {visible: true})
|
const element = await page.evaluateHandle(() => {
|
||||||
.then(() => {
|
return document.getElementsByTagName('div')[0]!;
|
||||||
return (divVisible = true);
|
});
|
||||||
});
|
await expect(
|
||||||
|
Promise.race([promise, createTimeout(40)])
|
||||||
|
).resolves.toBeFalsy();
|
||||||
|
await element.evaluate(e => {
|
||||||
|
e.style.setProperty('visibility', 'collapse');
|
||||||
|
});
|
||||||
|
await expect(
|
||||||
|
Promise.race([promise, createTimeout(40)])
|
||||||
|
).resolves.toBeFalsy();
|
||||||
|
await element.evaluate(e => {
|
||||||
|
e.style.removeProperty('visibility');
|
||||||
|
});
|
||||||
|
await expect(promise).resolves.toBeTruthy();
|
||||||
|
});
|
||||||
|
it('should wait for element to be visible (bounding box)', async () => {
|
||||||
|
const {page} = getTestState();
|
||||||
|
|
||||||
|
const promise = page.waitForSelector('div', {visible: true});
|
||||||
|
await page.setContent('<div style="width: 0">text</div>');
|
||||||
|
const element = await page.evaluateHandle(() => {
|
||||||
|
return document.getElementsByTagName('div')[0]!;
|
||||||
|
});
|
||||||
|
await expect(
|
||||||
|
Promise.race([promise, createTimeout(40)])
|
||||||
|
).resolves.toBeFalsy();
|
||||||
|
await element.evaluate(e => {
|
||||||
|
e.style.setProperty('height', '0');
|
||||||
|
e.style.removeProperty('width');
|
||||||
|
});
|
||||||
|
await expect(
|
||||||
|
Promise.race([promise, createTimeout(40)])
|
||||||
|
).resolves.toBeFalsy();
|
||||||
|
await element.evaluate(e => {
|
||||||
|
e.style.setProperty('position', 'absolute');
|
||||||
|
e.style.setProperty('right', '100vw');
|
||||||
|
e.style.removeProperty('height');
|
||||||
|
});
|
||||||
|
await expect(
|
||||||
|
Promise.race([promise, createTimeout(40)])
|
||||||
|
).resolves.toBeFalsy();
|
||||||
|
await element.evaluate(e => {
|
||||||
|
e.style.setProperty('left', '100vw');
|
||||||
|
e.style.removeProperty('right');
|
||||||
|
});
|
||||||
|
await expect(
|
||||||
|
Promise.race([promise, createTimeout(40)])
|
||||||
|
).resolves.toBeFalsy();
|
||||||
|
await element.evaluate(e => {
|
||||||
|
e.style.setProperty('top', '100vh');
|
||||||
|
e.style.removeProperty('left');
|
||||||
|
});
|
||||||
|
await expect(
|
||||||
|
Promise.race([promise, createTimeout(40)])
|
||||||
|
).resolves.toBeFalsy();
|
||||||
|
await element.evaluate(e => {
|
||||||
|
e.style.setProperty('bottom', '100vh');
|
||||||
|
e.style.removeProperty('top');
|
||||||
|
});
|
||||||
|
await expect(
|
||||||
|
Promise.race([promise, createTimeout(40)])
|
||||||
|
).resolves.toBeFalsy();
|
||||||
|
await element.evaluate(e => {
|
||||||
|
// Just peeking
|
||||||
|
e.style.setProperty('bottom', '99vh');
|
||||||
|
});
|
||||||
|
await expect(promise).resolves.toBeTruthy();
|
||||||
|
});
|
||||||
|
it('should wait for element to be visible recursively', async () => {
|
||||||
|
const {page} = getTestState();
|
||||||
|
|
||||||
|
const promise = page.waitForSelector('div#inner', {
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
await page.setContent(
|
await page.setContent(
|
||||||
`<div style='display: none; visibility: hidden;'><div id="inner">hi</div></div>`
|
`<div style='display: none; visibility: hidden;'><div id="inner">hi</div></div>`
|
||||||
);
|
);
|
||||||
expect(divVisible).toBe(false);
|
const element = await page.evaluateHandle(() => {
|
||||||
await page.evaluate(() => {
|
return document.getElementsByTagName('div')[0]!;
|
||||||
return document.querySelector('div')?.style.removeProperty('display');
|
|
||||||
});
|
});
|
||||||
expect(divVisible).toBe(false);
|
await expect(
|
||||||
await page.evaluate(() => {
|
Promise.race([promise, createTimeout(40)])
|
||||||
return document
|
).resolves.toBeFalsy();
|
||||||
.querySelector('div')
|
await element.evaluate(e => {
|
||||||
?.style.removeProperty('visibility');
|
return e.style.removeProperty('display');
|
||||||
});
|
});
|
||||||
expect(await waitForSelector).toBe(true);
|
await expect(
|
||||||
expect(divVisible).toBe(true);
|
Promise.race([promise, createTimeout(40)])
|
||||||
|
).resolves.toBeFalsy();
|
||||||
|
await element.evaluate(e => {
|
||||||
|
return e.style.removeProperty('visibility');
|
||||||
|
});
|
||||||
|
await expect(promise).resolves.toBeTruthy();
|
||||||
});
|
});
|
||||||
it('hidden should wait for visibility: hidden', async () => {
|
it('should wait for element to be hidden (visibility)', async () => {
|
||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
|
|
||||||
let divHidden = false;
|
const promise = page.waitForSelector('div', {hidden: true});
|
||||||
await page.setContent(`<div style='display: block;'></div>`);
|
await page.setContent(`<div style='display: block;'>text</div>`);
|
||||||
const waitForSelector = page
|
const element = await page.evaluateHandle(() => {
|
||||||
.waitForSelector('div', {hidden: true})
|
return document.getElementsByTagName('div')[0]!;
|
||||||
.then(() => {
|
|
||||||
return (divHidden = true);
|
|
||||||
});
|
|
||||||
await page.waitForSelector('div'); // do a round trip
|
|
||||||
expect(divHidden).toBe(false);
|
|
||||||
await page.evaluate(() => {
|
|
||||||
return document
|
|
||||||
.querySelector('div')
|
|
||||||
?.style.setProperty('visibility', 'hidden');
|
|
||||||
});
|
});
|
||||||
expect(await waitForSelector).toBe(true);
|
await expect(
|
||||||
expect(divHidden).toBe(true);
|
Promise.race([promise, createTimeout(40)])
|
||||||
|
).resolves.toBeFalsy();
|
||||||
|
await element.evaluate(e => {
|
||||||
|
return e.style.setProperty('visibility', 'hidden');
|
||||||
|
});
|
||||||
|
await expect(promise).resolves.toBeTruthy();
|
||||||
});
|
});
|
||||||
it('hidden should wait for display: none', async () => {
|
it('should wait for element to be hidden (display)', async () => {
|
||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
|
|
||||||
let divHidden = false;
|
const promise = page.waitForSelector('div', {hidden: true});
|
||||||
await page.setContent(`<div style='display: block;'></div>`);
|
await page.setContent(`<div style='display: block;'>text</div>`);
|
||||||
const waitForSelector = page
|
const element = await page.evaluateHandle(() => {
|
||||||
.waitForSelector('div', {hidden: true})
|
return document.getElementsByTagName('div')[0]!;
|
||||||
.then(() => {
|
|
||||||
return (divHidden = true);
|
|
||||||
});
|
|
||||||
await page.waitForSelector('div'); // do a round trip
|
|
||||||
expect(divHidden).toBe(false);
|
|
||||||
await page.evaluate(() => {
|
|
||||||
return document
|
|
||||||
.querySelector('div')
|
|
||||||
?.style.setProperty('display', 'none');
|
|
||||||
});
|
});
|
||||||
expect(await waitForSelector).toBe(true);
|
await expect(
|
||||||
expect(divHidden).toBe(true);
|
Promise.race([promise, createTimeout(40)])
|
||||||
|
).resolves.toBeFalsy();
|
||||||
|
await element.evaluate(e => {
|
||||||
|
return e.style.setProperty('display', 'none');
|
||||||
|
});
|
||||||
|
await expect(promise).resolves.toBeTruthy();
|
||||||
});
|
});
|
||||||
it('hidden should wait for removal', async () => {
|
it('should wait for element to be hidden (bounding box)', async () => {
|
||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
|
|
||||||
await page.setContent(`<div></div>`);
|
const promise = page.waitForSelector('div', {hidden: true});
|
||||||
let divRemoved = false;
|
await page.setContent('<div>text</div>');
|
||||||
const waitForSelector = page
|
const element = await page.evaluateHandle(() => {
|
||||||
.waitForSelector('div', {hidden: true})
|
return document.getElementsByTagName('div')[0]!;
|
||||||
.then(() => {
|
|
||||||
return (divRemoved = true);
|
|
||||||
});
|
|
||||||
await page.waitForSelector('div'); // do a round trip
|
|
||||||
expect(divRemoved).toBe(false);
|
|
||||||
await page.evaluate(() => {
|
|
||||||
return document.querySelector('div')?.remove();
|
|
||||||
});
|
});
|
||||||
expect(await waitForSelector).toBe(true);
|
await expect(
|
||||||
expect(divRemoved).toBe(true);
|
Promise.race([promise, createTimeout(40)])
|
||||||
|
).resolves.toBeFalsy();
|
||||||
|
await element.evaluate(e => {
|
||||||
|
e.style.setProperty('height', '0');
|
||||||
|
});
|
||||||
|
await expect(promise).resolves.toBeTruthy();
|
||||||
|
});
|
||||||
|
it('should wait for element to be hidden (removal)', async () => {
|
||||||
|
const {page} = getTestState();
|
||||||
|
|
||||||
|
const promise = page.waitForSelector('div', {hidden: true});
|
||||||
|
await page.setContent(`<div>text</div>`);
|
||||||
|
const element = await page.evaluateHandle(() => {
|
||||||
|
return document.getElementsByTagName('div')[0]!;
|
||||||
|
});
|
||||||
|
await expect(
|
||||||
|
Promise.race([promise, createTimeout(40, true)])
|
||||||
|
).resolves.toBeTruthy();
|
||||||
|
await element.evaluate(e => {
|
||||||
|
e.remove();
|
||||||
|
});
|
||||||
|
await expect(promise).resolves.toBeFalsy();
|
||||||
});
|
});
|
||||||
it('should return null if waiting to hide non-existing element', async () => {
|
it('should return null if waiting to hide non-existing element', async () => {
|
||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
@ -609,7 +683,7 @@ describe('waittask specs', function () {
|
|||||||
it('should have an error message specifically for awaiting an element to be hidden', async () => {
|
it('should have an error message specifically for awaiting an element to be hidden', async () => {
|
||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
|
|
||||||
await page.setContent(`<div></div>`);
|
await page.setContent(`<div>text</div>`);
|
||||||
let error!: Error;
|
let error!: Error;
|
||||||
await page
|
await page
|
||||||
.waitForSelector('div', {hidden: true, timeout: 10})
|
.waitForSelector('div', {hidden: true, timeout: 10})
|
||||||
@ -725,7 +799,7 @@ describe('waittask specs', function () {
|
|||||||
const {page} = getTestState();
|
const {page} = getTestState();
|
||||||
|
|
||||||
let divHidden = false;
|
let divHidden = false;
|
||||||
await page.setContent(`<div style='display: block;'></div>`);
|
await page.setContent(`<div style='display: block;'>text</div>`);
|
||||||
const waitForXPath = page
|
const waitForXPath = page
|
||||||
.waitForXPath('//div', {hidden: true})
|
.waitForXPath('//div', {hidden: true})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
Loading…
Reference in New Issue
Block a user