import puppeteer from 'puppeteer' export const E2E_TIMEOUT = 30 * 1000 const puppeteerOptions = process.env.CI ? { args: ['--no-sandbox', '--disable-setuid-sandbox'] } : {} const maxTries = 30 export const timeout = (n: number) => new Promise(r => setTimeout(r, n)) export async function expectByPolling( poll: () => Promise, expected: string ) { for (let tries = 0; tries < maxTries; tries++) { const actual = (await poll()) || '' if (actual.indexOf(expected) > -1 || tries === maxTries - 1) { expect(actual).toMatch(expected) break } else { await timeout(50) } } } export function setupPuppeteer() { let browser: puppeteer.Browser let page: puppeteer.Page beforeAll(async () => { browser = await puppeteer.launch(puppeteerOptions) }) beforeEach(async () => { page = await browser.newPage() await page.evaluateOnNewDocument(() => { localStorage.clear() }) page.on('console', e => { if (e.type() === 'error') { const err = e.args()[0] console.error( `Error from Puppeteer-loaded page:\n`, err._remoteObject.description ) } }) }) afterEach(async () => { await page.close() }) afterAll(async () => { await browser.close() }) async function click(selector: string, options?: puppeteer.ClickOptions) { await page.click(selector, options) } async function count(selector: string) { return (await page.$$(selector)).length } async function text(selector: string) { return await page.$eval(selector, node => node.textContent) } async function value(selector: string) { return await page.$eval(selector, node => (node as HTMLInputElement).value) } async function html(selector: string) { return await page.$eval(selector, node => node.innerHTML) } async function classList(selector: string) { return await page.$eval(selector, (node: any) => [...node.classList]) } async function children(selector: string) { return await page.$eval(selector, (node: any) => [...node.children]) } async function isVisible(selector: string) { const display = await page.$eval(selector, node => { return window.getComputedStyle(node).display }) return display !== 'none' } async function isChecked(selector: string) { return await page.$eval( selector, node => (node as HTMLInputElement).checked ) } async function isFocused(selector: string) { return await page.$eval(selector, node => node === document.activeElement) } async function setValue(selector: string, value: string) { await page.$eval( selector, (node, value) => { ;(node as HTMLInputElement).value = value as string node.dispatchEvent(new Event('input')) }, value ) } async function typeValue(selector: string, value: string) { const el = (await page.$(selector))! await el.evaluate(node => ((node as HTMLInputElement).value = '')) await el.type(value) } async function enterValue(selector: string, value: string) { const el = (await page.$(selector))! await el.evaluate(node => ((node as HTMLInputElement).value = '')) await el.type(value) await el.press('Enter') } async function clearValue(selector: string) { return await page.$eval( selector, node => ((node as HTMLInputElement).value = '') ) } function timeout(time: number) { return page.evaluate(time => { return new Promise(r => { setTimeout(r, time) }) }, time) } function nextFrame() { return page.evaluate(() => { return new Promise(resolve => { requestAnimationFrame(() => { requestAnimationFrame(resolve) }) }) }) } return { page: () => page, click, count, text, value, html, classList, children, isVisible, isChecked, isFocused, setValue, typeValue, enterValue, clearValue, timeout, nextFrame } }