1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00
metamask-extension/test/e2e/helpers.js
Mark Stacey 0f4c0286a4
Improve e2e helper functions (#7750)
* Improve `openNewPage` helper function

The two delays were removed, and the window handle for the new page is
now returned. This was made possible with the new `newWindow` function
added in `v4.0.0-alpha.3` of `selenium-webdriver`.

* Replace recursion with loops

This should result in far more pleasant stack traces for any
exceptions in these functions. It might also be faster. These functions
seem easier to understand as loops as well.

* Remove unused string parameter

The `closeAllWindowHandlesExcept` function has been simplified by
removing a branch that handles the case where the `exceptions`
parameter given is a string. That parameter is never a string.
2020-01-07 10:46:03 -04:00

193 lines
7.0 KiB
JavaScript

const fs = require('fs')
const mkdirp = require('mkdirp')
const pify = require('pify')
const assert = require('assert')
const { until } = require('selenium-webdriver')
const { buildWebDriver } = require('./webdriver')
const fetchMockResponses = require('./fetch-mocks.json')
const tinyDelayMs = 200
const regularDelayMs = tinyDelayMs * 2
const largeDelayMs = regularDelayMs * 2
module.exports = {
assertElementNotPresent,
checkBrowserForConsoleErrors,
closeAllWindowHandlesExcept,
delay,
findElement,
findElements,
openNewPage,
switchToWindowWithTitle,
verboseReportOnFailure,
waitUntilXWindowHandles,
setupFetchMocking,
prepareExtensionForTesting,
tinyDelayMs,
regularDelayMs,
largeDelayMs,
}
async function prepareExtensionForTesting ({ responsive, port } = {}) {
const browser = process.env.SELENIUM_BROWSER
const extensionPath = `dist/${browser}`
const { driver, extensionId, extensionUrl } = await buildWebDriver({ browser, extensionPath, responsive, port })
// Depending on the state of the application built into the above directory (extPath) and the value of
// METAMASK_DEBUG we will see different post-install behaviour and possibly some extra windows. Here we
// are closing any extraneous windows to reset us to a single window before continuing.
// wait an extra long time so any slow popups can trigger
await delay(4 * largeDelayMs)
const [tab1] = await driver.getAllWindowHandles()
await closeAllWindowHandlesExcept(driver, [tab1])
await driver.switchTo().window(tab1)
await driver.get(extensionUrl)
return { driver, extensionId, extensionUrl }
}
async function setupFetchMocking (driver) {
// define fetchMocking script, to be evaluated in the browser
function fetchMocking (fetchMockResponses) {
window.origFetch = window.fetch.bind(window)
window.fetch = async (...args) => {
const url = args[0]
if (url === 'https://ethgasstation.info/json/ethgasAPI.json') {
return { json: async () => clone(fetchMockResponses.ethGasBasic) }
} else if (url === 'https://ethgasstation.info/json/predictTable.json') {
return { json: async () => clone(fetchMockResponses.ethGasPredictTable) }
} else if (url.match(/chromeextensionmm/)) {
return { json: async () => clone(fetchMockResponses.metametrics) }
}
return window.origFetch(...args)
}
if (window.chrome && window.chrome.webRequest) {
window.chrome.webRequest.onBeforeRequest.addListener(cancelInfuraRequest, { urls: ['https://*.infura.io/*'] }, ['blocking'])
}
function cancelInfuraRequest (requestDetails) {
console.log(`fetchMocking - Canceling request: "${requestDetails.url}"`)
return { cancel: true }
}
function clone (obj) {
return JSON.parse(JSON.stringify(obj))
}
}
// fetchMockResponses are parsed last minute to ensure that objects are uniquely instantiated
const fetchMockResponsesJson = JSON.stringify(fetchMockResponses)
// eval the fetchMocking script in the browser
await driver.executeScript(`(${fetchMocking})(${fetchMockResponsesJson})`)
}
async function checkBrowserForConsoleErrors (driver) {
const ignoredLogTypes = ['WARNING']
const ignoredErrorMessages = [
// React throws error warnings on "dataset", but still sets the data-* properties correctly
'Warning: Unknown prop `dataset` on ',
// Third-party Favicon 404s show up as errors
'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)',
// React Development build - known issue blocked by test build sys
'Warning: It looks like you\'re using a minified copy of the development build of React.',
// Redux Development build - known issue blocked by test build sys
'This means that you are running a slower development build of Redux.',
]
const browserLogs = await driver.manage().logs().get('browser')
const errorEntries = browserLogs.filter(entry => !ignoredLogTypes.includes(entry.level.toString()))
const errorObjects = errorEntries.map(entry => entry.toJSON())
return errorObjects.filter(entry => !ignoredErrorMessages.some(message => entry.message.includes(message)))
}
async function verboseReportOnFailure (driver, test) {
let artifactDir
if (process.env.SELENIUM_BROWSER === 'chrome') {
artifactDir = `./test-artifacts/chrome/${test.title}`
} else if (process.env.SELENIUM_BROWSER === 'firefox') {
artifactDir = `./test-artifacts/firefox/${test.title}`
}
const filepathBase = `${artifactDir}/test-failure`
await pify(mkdirp)(artifactDir)
const screenshot = await driver.takeScreenshot()
await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
const htmlSource = await driver.getPageSource()
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
}
function delay (time) {
return new Promise(resolve => setTimeout(resolve, time))
}
async function findElement (driver, by, timeout = 10000) {
return driver.wait(until.elementLocated(by), timeout)
}
async function findElements (driver, by, timeout = 10000) {
return driver.wait(until.elementsLocated(by), timeout)
}
async function openNewPage (driver, url) {
const newHandle = await driver.switchTo().newWindow()
await driver.get(url)
return newHandle
}
async function waitUntilXWindowHandles (driver, x, delayStep = 1000, timeout = 5000) {
let timeElapsed = 0
while (timeElapsed <= timeout) {
const windowHandles = await driver.getAllWindowHandles()
if (windowHandles.length === x) {
return
}
await delay(delayStep)
timeElapsed += delayStep
}
throw new Error('waitUntilXWindowHandles timed out polling window handles')
}
async function switchToWindowWithTitle (driver, title, windowHandles) {
if (!windowHandles) {
windowHandles = await driver.getAllWindowHandles()
}
for (const handle of windowHandles) {
await driver.switchTo().window(handle)
const handleTitle = await driver.getTitle()
if (handleTitle === title) {
return handle
}
}
throw new Error('No window with title: ' + title)
}
/**
* Closes all windows except those in the given list of exceptions
* @param {object} driver the WebDriver instance
* @param {Array<string>} exceptions the list of window handle exceptions
* @param {Array?} windowHandles the full list of window handles
* @returns {Promise<void>}
*/
async function closeAllWindowHandlesExcept (driver, exceptions, windowHandles) {
windowHandles = windowHandles || await driver.getAllWindowHandles()
for (const handle of windowHandles) {
if (!exceptions.includes(handle)) {
await driver.switchTo().window(handle)
await delay(1000)
await driver.close()
await delay(1000)
}
}
}
async function assertElementNotPresent (webdriver, driver, by) {
let dataTab
try {
dataTab = await findElement(driver, by, 4000)
} catch (err) {
assert(err instanceof webdriver.error.NoSuchElementError || err instanceof webdriver.error.TimeoutError)
}
assert.ok(!dataTab, 'Found element that should not be present')
}