-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[UIE-205] Puppeteer upgrade (version 22.7.1) #5117
Changes from all commits
29b46de
6aff1c8
1da7bf0
e2616ec
d482f3f
11db55c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,11 @@ | ||
/* eslint-disable prefer-template */ | ||
const printWarning = (message) => { | ||
if (process.env.warningPrinted) { | ||
return; | ||
} | ||
console.warn('\n\x1b[1m' /* bold */ + '╔'.padEnd(80, '═') + '╗'); | ||
message.forEach((line) => { | ||
console.warn(`║ ${line}`.padEnd(80) + '║'); | ||
}); | ||
console.warn('╚'.padEnd(80, '═') + '╝'); | ||
console.warn('\x1b[0m' /* not-bold */); | ||
process.env.warningPrinted = true; | ||
}; | ||
|
||
module.exports = { | ||
browserContext: 'incognito', | ||
launch: { | ||
devtools: process.env.DEVTOOLS === 'true', | ||
headless: process.env.HEADLESS !== 'false', | ||
slowMo: process.env.SLOW === 'true' ? 250 : undefined, | ||
defaultViewport: { width: 1200, height: 800 }, | ||
// Workaround for issue when accessing cross domain iframes in puppeteer while not headless: | ||
// https://github.com/GoogleChrome/puppeteer/issues/4960 | ||
args: (({ headfull, devtools }) => { | ||
const flag = '--disable-features=site-per-process'; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. None of this is needed anymore-- I ran the IA analysis test locally (which interacts with an iframe) with both HEADFUL and DEVTOOLS, and it passed. |
||
if (headfull && devtools) { | ||
printWarning([ | ||
'iframes Issue'.padStart(40), | ||
'Dev tools cannot be opened in headless mode with the', | ||
`${flag} flag set. If you are running tests`, | ||
'with iframes they will fail with dev tools turned on.', | ||
]); | ||
return []; | ||
} | ||
if (headfull) { | ||
printWarning([ | ||
'Chromium flag set'.padStart(40), | ||
`Headfull mode also sets the ${flag} flag`, | ||
'This allows iframe tests to work in Headfull mode.', | ||
'If you notice unexpected side effects in your tests', | ||
'you can disable this flag in src/jest-puppeteer.config.js', | ||
]); | ||
return [flag]; | ||
} | ||
return []; | ||
})({ headfull: process.env.HEADLESS === 'false', devtools: process.env.DEVTOOLS === 'true' }), | ||
protocolTimeout: 720_000, // 12 minutes | ||
args: [], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At some point, Puppeteer decreased their default protocolTimeout. I needed to increase this for the IA analysis tests, which wait for a very long time for Jupyter or RStudio to launch and be ready to interact with. |
||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,21 +8,21 @@ | |
"test-flakes": "FLAKES=true yarn test-local --runInBand" | ||
}, | ||
"devDependencies": { | ||
"jest": "^27.4.3", | ||
"jest-environment-puppeteer": "^6.0.3", | ||
"jest": "^29.7.0", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I increased these all to their most recent version. |
||
"jest-environment-puppeteer": "^10.1.1", | ||
"jest-junit": "^13.0.0", | ||
"jest-puppeteer": "^6.0.2", | ||
"jest-puppeteer": "^10.1.1", | ||
"node-fetch": "^2.6.7", | ||
"prompts": "^2.4.1", | ||
"puppeteer-cluster": "^0.23.0" | ||
"puppeteer-cluster": "^0.24.0" | ||
}, | ||
"dependencies": { | ||
"@axe-core/puppeteer": "^4.6.0", | ||
"date-fns": "^2.24.0", | ||
"google-auth-library": "^7.9.2", | ||
"lodash": "^4.17.21", | ||
"p-retry": "^4.6.1", | ||
"puppeteer": "^13.1.3", | ||
"puppeteer": "22.7.1", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the last Puppeteer version that works with the IA iframes (using Chrome version 124.0.6367.78). All later versions I tried failed in the same way: broken computer icon in the iframe and a message about being unable to access dev leonardo. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jdcanas just pinging you in case you might know, but it sounds like we eventually will need to rewrite our IA UI Integration tests to be better compatible with pupeteer? |
||
"qs": "^6.10.1", | ||
"uuid": "^8.3.2" | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,7 +40,7 @@ const billingProjectsPage = (testPage, testUrl) => { | |
|
||
assertChartValue: async (number, workspaceName, category, cost) => { | ||
// This checks the accessible text for chart values. | ||
await testPage.waitForXPath(`(//*[@role="img"])[contains(@aria-label,"${number}. Workspace ${workspaceName}, ${category}: ${cost}.")]`); | ||
await testPage.waitForSelector(`[role="img"][aria-label*="${number}. Workspace ${workspaceName}, ${category}: ${cost}."]`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
}, | ||
}; | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,14 +30,14 @@ const testFindWorkflowFn = _.flow( | |
without these lines it will redirect to dev-Terra even if started out at localhost:3000-Terra */ | ||
if (testUrl === 'http://localhost:3000') { | ||
await findElement(page, clickable({ textContains: 'Yes' })); | ||
const yesButtonHrefDetails = (await page.$x('//a[contains(text(), "Yes")]/@href'))[0]; | ||
const yesButtonHrefDetails = (await page.$$('xpath///a[contains(text(), "Yes")]/@href'))[0]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename of this method, and you have to specify 'xpath/' at the beginning if using an xpath. |
||
const href = await page.evaluate((yesButton) => yesButton.textContent, yesButtonHrefDetails); | ||
const redirectURL = _.replace(/https:\/\/bvdp-saturn-(dev|staging).appspot.com/, testUrl, href); | ||
await gotoPage(page, redirectURL); | ||
} else { | ||
await click(page, clickable({ textContains: 'Yes' })); | ||
} | ||
await page.waitForXPath('//*[@id="signInButton"]', { visible: true }); | ||
await page.waitForSelector('xpath///*[@id="signInButton"]', { visible: true }); | ||
}; | ||
|
||
await Promise.all([page.waitForNavigation(), backToTerra()]); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,7 +44,7 @@ const waitForFn = async ({ fn, interval = 2000, timeout = 10000 }) => { | |
}; | ||
|
||
const findIframe = async (page, iframeXPath = '//*[@role="main"]/iframe', options) => { | ||
const iframeNode = await page.waitForXPath(iframeXPath, defaultToVisibleTrue(options)); | ||
const iframeNode = await page.waitForSelector(`xpath/${iframeXPath}`, defaultToVisibleTrue(options)); | ||
const srcHandle = await iframeNode.getProperty('src'); | ||
const src = await srcHandle.jsonValue(); | ||
const hasFrame = () => page.frames().find((frame) => frame.url().includes(src)); | ||
|
@@ -53,7 +53,7 @@ const findIframe = async (page, iframeXPath = '//*[@role="main"]/iframe', option | |
}; | ||
|
||
const findInGrid = (page, textContains, options) => { | ||
return page.waitForXPath(`//*[@role="table"][contains(normalize-space(.),"${textContains}")]`, defaultToVisibleTrue(options)); | ||
return page.waitForSelector(`xpath///*[@role="table"][contains(normalize-space(.),"${textContains}")]`, defaultToVisibleTrue(options)); | ||
}; | ||
|
||
const getClickablePath = (path, text, textContains, isDescendant = false) => { | ||
|
@@ -127,26 +127,26 @@ const assertRowHas = async (page, { tableName, expectedColumnValues, withKey: { | |
const clickTableCell = async (page, { tableName, columnHeader, text, isDescendant = false }, options) => { | ||
const tableCellPath = await getTableCellByContents(page, { tableName, columnHeader, text, isDescendant }); | ||
const xpath = `${tableCellPath}[@role="button" or @role="link" or @role="checkbox"]`; | ||
return (await page.waitForXPath(xpath, options)).click(); | ||
return (await page.waitForSelector(`xpath/${xpath}`, options)).click(); | ||
}; | ||
|
||
const click = async (page, xpath, options) => { | ||
try { | ||
return (await page.waitForXPath(xpath, defaultToVisibleTrue(options))).click(); | ||
return (await page.waitForSelector(`xpath/${xpath}`, defaultToVisibleTrue(options))).click(); | ||
} catch (e) { | ||
if (e.message.includes('Node is detached from document')) { | ||
return (await page.waitForXPath(xpath, defaultToVisibleTrue(options))).click(); | ||
return (await page.waitForSelector(`xpath/${xpath}`, defaultToVisibleTrue(options))).click(); | ||
} | ||
throw e; | ||
} | ||
}; | ||
|
||
const findText = (page, textContains, options) => { | ||
return page.waitForXPath(`//*[contains(normalize-space(.),"${textContains}")]`, defaultToVisibleTrue(options)); | ||
return page.waitForSelector(`xpath///*[contains(normalize-space(.),"${textContains}")]`, defaultToVisibleTrue(options)); | ||
}; | ||
|
||
const getLabelledTextInputValue = async (page, xpath) => { | ||
const inputLabel = await page.waitForXPath(xpath); | ||
const inputLabel = await page.waitForSelector(`xpath/${xpath}`); | ||
const labelFor = await inputLabel?.evaluate((l) => l.getAttribute('for')); | ||
const input = await page.$(`#${labelFor}`); | ||
return await input?.evaluate((i) => i.value); | ||
|
@@ -185,7 +185,7 @@ const label = ({ labelContains }) => { | |
}; | ||
|
||
const fillIn = async (page, xpath, text, options) => { | ||
const input = await page.waitForXPath(xpath, defaultToVisibleTrue(options)); | ||
const input = await page.waitForSelector(`xpath/${xpath}`, defaultToVisibleTrue(options)); | ||
await input.click(); | ||
|
||
// Actually type the text | ||
|
@@ -222,15 +222,15 @@ const select = async (page, labelContains, text) => { | |
}; | ||
|
||
const waitForNoSpinners = (page, { timeout = 30000 } = {}) => { | ||
return page.waitForXPath('//*[@data-icon="loadingSpinner"]', { hidden: true, timeout }); | ||
return page.waitForSelector('xpath///*[@data-icon="loadingSpinner"]', { hidden: true, timeout }); | ||
}; | ||
|
||
const waitForNoModal = (page, { timeout = 30000 } = {}) => { | ||
return page.waitForXPath('//*[contains(@class, "ReactModal__Overlay")]', { hidden: true, timeout }); | ||
return page.waitForSelector('.ReactModal__Overlay', { hidden: true, timeout }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simplification |
||
}; | ||
|
||
const waitForModal = (page, { timeout = 30000 } = {}) => { | ||
return page.waitForXPath('//*[contains(@class, "ReactModal__Overlay")]', { hidden: false, timeout }); | ||
return page.waitForSelector('.ReactModal__Overlay', { hidden: false, timeout }); | ||
}; | ||
|
||
// Puppeteer works by internally using MutationObserver. We are setting up the listener before | ||
|
@@ -240,7 +240,7 @@ const noSpinnersAfter = async (page, { action, debugMessage, timeout = 30000 }) | |
if (debugMessage) { | ||
console.log(`About to perform an action and wait for spinners. \n\tDebug message: ${debugMessage}`); | ||
} | ||
const foundSpinner = page.waitForXPath('//*[@data-icon="loadingSpinner"]', { timeout }); | ||
const foundSpinner = page.waitForSelector('xpath///*[@data-icon="loadingSpinner"]', { timeout }); | ||
await Promise.all([foundSpinner, action()]); | ||
return waitForNoSpinners(page, { timeout }); | ||
}; | ||
|
@@ -252,7 +252,7 @@ const delay = (ms) => { | |
/** Dismiss all popup notifications, including errors. */ | ||
const dismissAllNotifications = async (page) => { | ||
await delay(3000); // delayed for any alerts to show | ||
const notificationCloseButtons = await page.$x('(//a | //*[@role="button"] | //button)[contains(@aria-label,"Dismiss")]'); | ||
const notificationCloseButtons = await page.$$('xpath/(//a | //*[@role="button"] | //button)[contains(@aria-label,"Dismiss")]'); | ||
|
||
await Promise.all(notificationCloseButtons.map((handle) => handle.click())); | ||
|
||
|
@@ -262,8 +262,8 @@ const dismissAllNotifications = async (page) => { | |
/** Dismiss popup notifications, except for errors. */ | ||
const dismissInfoNotifications = async (page) => { | ||
await delay(3000); // delayed for any alerts to show | ||
const notificationCloseButtons = await page.$x( | ||
'(//a | //*[@role="button"] | //button)[contains(@aria-label,"Dismiss") and not(contains(@aria-label,"error"))]' | ||
const notificationCloseButtons = await page.$$( | ||
'xpath/(//a | //*[@role="button"] | //button)[contains(@aria-label,"Dismiss") and not(contains(@aria-label,"error"))]' | ||
); | ||
|
||
await Promise.all(notificationCloseButtons.map((handle) => handle.click())); | ||
|
@@ -275,14 +275,14 @@ const dismissInfoNotifications = async (page) => { | |
const dismissNPSSurvey = async (page) => { | ||
let element; | ||
try { | ||
element = await page.waitForXPath('//iframe[@aria-label="NPS Survey"]', { timeout: 1000 }); | ||
element = await page.waitForSelector('xpath///iframe[@aria-label="NPS Survey"]', { timeout: 1000 }); | ||
} catch (e) { | ||
return; // NPS survey was not found | ||
} | ||
try { | ||
console.log('dismissing NPS survey'); | ||
const iframe = await element.contentFrame(); | ||
const [closeButton] = await iframe.$x('.//*[normalize-space(.)="Ask Me Later"]'); | ||
const [closeButton] = await iframe.$$('xpath/.//*[normalize-space(.)="Ask Me Later"]'); | ||
await closeButton.evaluate((button) => button.click()); | ||
await delay(500); // delayed for survey to animate off | ||
} catch (e) { | ||
|
@@ -297,7 +297,7 @@ const signIntoTerra = async (page, { token, testUrl }) => { | |
if (testUrl) { | ||
await gotoPage(page, testUrl); | ||
} else { | ||
await page.waitForXPath('//*[contains(normalize-space(.),"Loading Terra")]', { hidden: true }); | ||
await page.waitForSelector('xpath///*[contains(normalize-space(.),"Loading Terra")]', { hidden: true }); | ||
} | ||
|
||
await waitForNoSpinners(page); | ||
|
@@ -311,11 +311,7 @@ const signIntoTerra = async (page, { token, testUrl }) => { | |
}; | ||
|
||
const findElement = (page, xpath, options) => { | ||
return page.waitForXPath(xpath, defaultToVisibleTrue(options)); | ||
}; | ||
|
||
const findErrorPopup = (page, options) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
return page.waitForXPath('(//a | //*[@role="button"] | //button)[contains(@aria-label,"Dismiss")][contains(@aria-label,"error")]', options); | ||
return page.waitForSelector(`xpath/${xpath}`, defaultToVisibleTrue(options)); | ||
}; | ||
|
||
const heading = ({ level, text, textContains, isDescendant = false }) => { | ||
|
@@ -334,7 +330,7 @@ const heading = ({ level, text, textContains, isDescendant = false }) => { | |
}; | ||
|
||
const findHeading = (page, xpath, options) => { | ||
return page.waitForXPath(xpath, options); | ||
return page.waitForSelector(`xpath/${xpath}`, options); | ||
}; | ||
|
||
const svgText = ({ textContains }) => { | ||
|
@@ -348,7 +344,7 @@ const navChild = (text) => { | |
const assertNavChildNotFound = async (page, text) => { | ||
let found = false; | ||
try { | ||
await page.waitForXPath(navChild(text), { timeout: 5 * 1000 }); | ||
await page.waitForSelector(`xpath/${navChild(text)}`, { timeout: 5 * 1000 }); | ||
found = true; | ||
} catch (e) {} | ||
if (found) { | ||
|
@@ -364,25 +360,19 @@ const findInDataTableRow = (page, entityName, text) => { | |
return findElement(page, elementInDataTableRow(entityName, text)); | ||
}; | ||
|
||
const findButtonInDialogByAriaLabel = (page, ariaLabelText) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused. |
||
return page.waitForXPath(`//*[@role="dialog" and @aria-hidden="false"]//*[@role="button" and contains(@aria-label,"${ariaLabelText}")]`, { | ||
visible: true, | ||
}); | ||
}; | ||
|
||
/** Waits for a menu element to expand (or collapse if isExpanded=false) */ | ||
const waitForMenu = (page, { labelContains, isExpanded = true, ...options }) => { | ||
return page.waitForXPath( | ||
`//*[contains(@aria-label,"${labelContains}") or @id=//label[contains(normalize-space(.),"${labelContains}")]/@for or @aria-labelledby=//*[contains(normalize-space(.),"${labelContains}")]/@id][@aria-expanded="${isExpanded}"]`, | ||
defaultToVisibleTrue(options) | ||
); | ||
const labelContainsSelector = `[aria-label*="${labelContains}"], [id="${labelContains}"], [aria-labelledby*="${labelContains}"]`; | ||
const expandedSelector = `[aria-expanded="${isExpanded}"]`; | ||
|
||
return page.waitForSelector(`${labelContainsSelector}${expandedSelector}`, defaultToVisibleTrue(options)); | ||
}; | ||
|
||
const openError = async (page) => { | ||
// close out any non-error notifications first | ||
await dismissInfoNotifications(page); | ||
|
||
const errorDetails = await page.$x('(//a | //*[@role="button"] | //button)[contains(normalize-space(.),"Details")]'); | ||
const errorDetails = await page.$$('xpath/(//a | //*[@role="button"] | //button)[contains(normalize-space(.),"Details")]'); | ||
|
||
!!errorDetails[0] && (await errorDetails[0].click()); | ||
|
||
|
@@ -526,7 +516,7 @@ const gotoPage = async (page, url) => { | |
if (httpResponse && !(httpResponse.ok() || httpResponse.status() === 304)) { | ||
throw new Error(`Error loading URL: ${url}. Http response status: ${httpResponse.statusText()}`); | ||
} | ||
await page.waitForXPath('//*[contains(normalize-space(.),"Loading Terra")]', { hidden: true }); | ||
await page.waitForSelector('xpath///*[contains(normalize-space(.),"Loading Terra")]', { hidden: true }); | ||
} catch (e) { | ||
console.error(e); | ||
// Stop page loading, as if you hit "X" in the browser. ignore exception. | ||
|
@@ -610,9 +600,7 @@ module.exports = { | |
enablePageLogging, | ||
fillIn, | ||
fillInReplace, | ||
findButtonInDialogByAriaLabel, | ||
findElement, | ||
findErrorPopup, | ||
findHeading, | ||
findIframe, | ||
findInDataTableRow, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Puppeteer changed how this is exported.