A Metamask fork with Infura removed and default networks editable
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ciphermask/test/e2e/webdriver/driver.js

230 lines
6.7 KiB

const { promises: fs } = require('fs')
const { until, error: webdriverError } = require('selenium-webdriver')
const { strict: assert } = require('assert')
class Driver {
/**
* @param {!ThenableWebDriver} driver - A {@code WebDriver} instance
* @param {string} browser - The type of browser this driver is controlling
* @param {number} timeout
*/
constructor (driver, browser, extensionUrl, timeout = 10000) {
this.driver = driver
this.browser = browser
this.extensionUrl = extensionUrl
this.timeout = timeout
}
async delay (time) {
await new Promise((resolve) => setTimeout(resolve, time))
}
async wait (condition, timeout = this.timeout) {
await this.driver.wait(condition, timeout)
}
async quit () {
await this.driver.quit()
}
// Element interactions
async findElement (locator) {
return await this.driver.wait(until.elementLocated(locator), this.timeout)
}
async findVisibleElement (locator) {
const element = await this.findElement(locator)
await this.driver.wait(until.elementIsVisible(element), this.timeout)
return element
}
async findClickableElement (locator) {
const element = await this.findElement(locator)
await Promise.all([
this.driver.wait(until.elementIsVisible(element), this.timeout),
this.driver.wait(until.elementIsEnabled(element), this.timeout),
])
return element
}
async findElements (locator) {
return await this.driver.wait(until.elementsLocated(locator), this.timeout)
}
async findClickableElements (locator) {
const elements = await this.findElements(locator)
await Promise.all(elements
.reduce((acc, element) => {
acc.push(
this.driver.wait(until.elementIsVisible(element), this.timeout),
this.driver.wait(until.elementIsEnabled(element), this.timeout),
)
return acc
}, []),
)
return elements
}
async clickElement (locator) {
const element = await this.findClickableElement(locator)
await element.click()
}
async clickPoint (locator, x, y) {
const element = await this.findElement(locator)
await this.driver
.actions()
.move({ origin: element, x, y })
.click()
.perform()
}
async scrollToElement (element) {
await this.driver.executeScript('arguments[0].scrollIntoView(true)', element)
}
async assertElementNotPresent (locator) {
let dataTab
try {
dataTab = await this.findElement(locator)
} catch (err) {
assert(err instanceof webdriverError.NoSuchElementError || err instanceof webdriverError.TimeoutError)
}
assert.ok(!dataTab, 'Found element that should not be present')
}
// Navigation
async navigate (page = Driver.PAGES.HOME) {
return await this.driver.get(`${this.extensionUrl}/${page}.html`)
}
// Metrics
async collectMetrics () {
return await this.driver.executeScript(collectMetrics)
}
// Window management
async openNewPage (url) {
const newHandle = await this.driver.switchTo().newWindow()
await this.driver.get(url)
return newHandle
}
async switchToWindow (handle) {
await this.driver.switchTo().window(handle)
}
async getAllWindowHandles () {
return await this.driver.getAllWindowHandles()
}
async waitUntilXWindowHandles (x, delayStep = 1000, timeout = 5000) {
let timeElapsed = 0
while (timeElapsed <= timeout) {
const windowHandles = await this.driver.getAllWindowHandles()
if (windowHandles.length === x) {
return
}
await this.delay(delayStep)
timeElapsed += delayStep
}
throw new Error('waitUntilXWindowHandles timed out polling window handles')
}
async switchToWindowWithTitle (title, windowHandles) {
if (!windowHandles) {
windowHandles = await this.driver.getAllWindowHandles()
}
for (const handle of windowHandles) {
await this.driver.switchTo().window(handle)
const handleTitle = await this.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 {Array<string>} exceptions - The list of window handle exceptions
* @param {Array} [windowHandles] - The full list of window handles
* @returns {Promise<void>}
*/
async closeAllWindowHandlesExcept (exceptions, windowHandles) {
windowHandles = windowHandles || await this.driver.getAllWindowHandles()
for (const handle of windowHandles) {
if (!exceptions.includes(handle)) {
await this.driver.switchTo().window(handle)
await this.delay(1000)
await this.driver.close()
await this.delay(1000)
}
}
}
// Error handling
async verboseReportOnFailure (title) {
const artifactDir = `./test-artifacts/${this.browser}/${title}`
const filepathBase = `${artifactDir}/test-failure`
await fs.mkdir(artifactDir, { recursive: true })
const screenshot = await this.driver.takeScreenshot()
await fs.writeFile(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
const htmlSource = await this.driver.getPageSource()
await fs.writeFile(`${filepathBase}-dom.html`, htmlSource)
const uiState = await this.driver.executeScript(() => window.getCleanAppState())
await fs.writeFile(`${filepathBase}-state.json`, JSON.stringify(uiState, null, 2))
}
async checkBrowserForConsoleErrors () {
const ignoredLogTypes = ['WARNING']
const ignoredErrorMessages = [
// Third-party Favicon 404s show up as errors
'favicon.ico - Failed to load resource: the server responded with a status of 404 (Not Found)',
]
const browserLogs = await this.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)))
}
}
function collectMetrics () {
const results = {
paint: {},
navigation: [],
}
window.performance.getEntriesByType('paint').forEach((paintEntry) => {
results.paint[paintEntry.name] = paintEntry.startTime
})
window.performance.getEntriesByType('navigation').forEach((navigationEntry) => {
results.navigation.push({
domContentLoaded: navigationEntry.domContentLoadedEventEnd,
load: navigationEntry.loadEventEnd,
domInteractive: navigationEntry.domInteractive,
redirectCount: navigationEntry.redirectCount,
type: navigationEntry.type,
})
})
return results
}
Driver.PAGES = {
HOME: 'home',
NOTIFICATION: 'notification',
POPUP: 'popup',
}
module.exports = Driver