From 7662bb6e89986529622f3fabccb80b6ecbb7405c Mon Sep 17 00:00:00 2001 From: kobenguyent Date: Thu, 2 Jan 2025 15:35:24 +0100 Subject: [PATCH 1/6] fix: error thrown when element text is an empty string --- lib/helper/Playwright.js | 2400 +++++++++++++++++------------------ test/data/app/view/info.php | 9 +- test/helper/webapi.js | 8 + 3 files changed, 1168 insertions(+), 1249 deletions(-) diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index 37c23dd35..f043e52d5 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -1,17 +1,17 @@ -const path = require('path') -const fs = require('fs') - -const Helper = require('@codeceptjs/helper') -const { v4: uuidv4 } = require('uuid') -const assert = require('assert') -const promiseRetry = require('promise-retry') -const Locator = require('../locator') -const recorder = require('../recorder') -const stringIncludes = require('../assert/include').includes -const { urlEquals } = require('../assert/equal') -const { equals } = require('../assert/equal') -const { empty } = require('../assert/empty') -const { truth } = require('../assert/truth') +const path = require('path'); +const fs = require('fs'); + +const Helper = require('@codeceptjs/helper'); +const { v4: uuidv4 } = require('uuid'); +const assert = require('assert'); +const promiseRetry = require('promise-retry'); +const Locator = require('../locator'); +const recorder = require('../recorder'); +const stringIncludes = require('../assert/include').includes; +const { urlEquals } = require('../assert/equal'); +const { equals } = require('../assert/equal'); +const { empty } = require('../assert/empty'); +const { truth } = require('../assert/truth'); const { xpathLocator, ucfirst, @@ -24,44 +24,28 @@ const { clearString, requireWithFallback, normalizeSpacesInString, -} = require('../utils') -const { isColorProperty, convertColorToRGBA } = require('../colorUtils') -const ElementNotFound = require('./errors/ElementNotFound') -const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnectionRefused') -const Popup = require('./extras/Popup') -const Console = require('./extras/Console') -const { findReact, findVue, findByPlaywrightLocator } = require('./extras/PlaywrightReactVueLocator') - -let playwright -let perfTiming -let defaultSelectorEnginesInitialized = false - -const popupStore = new Popup() -const consoleLogStore = new Console() -const availableBrowsers = ['chromium', 'webkit', 'firefox', 'electron'] +} = require('../utils'); +const { isColorProperty, convertColorToRGBA } = require('../colorUtils'); +const ElementNotFound = require('./errors/ElementNotFound'); +const RemoteBrowserConnectionRefused = require('./errors/RemoteBrowserConnectionRefused'); +const Popup = require('./extras/Popup'); +const Console = require('./extras/Console'); +const { findReact, findVue, findByPlaywrightLocator } = require('./extras/PlaywrightReactVueLocator'); -const { - setRestartStrategy, - restartsSession, - restartsContext, - restartsBrowser, -} = require('./extras/PlaywrightRestartOpts') -const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine') -const { - seeElementError, - dontSeeElementError, - dontSeeElementInDOMError, - seeElementInDOMError, -} = require('./errors/ElementAssertion') -const { - dontSeeTraffic, - seeTraffic, - grabRecordedNetworkTraffics, - stopRecordingTraffic, - flushNetworkTraffics, -} = require('./network/actions') +let playwright; +let perfTiming; +let defaultSelectorEnginesInitialized = false; + +const popupStore = new Popup(); +const consoleLogStore = new Console(); +const availableBrowsers = ['chromium', 'webkit', 'firefox', 'electron']; + +const { setRestartStrategy, restartsSession, restartsContext, restartsBrowser } = require('./extras/PlaywrightRestartOpts'); +const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine'); +const { seeElementError, dontSeeElementError, dontSeeElementInDOMError, seeElementInDOMError } = require('./errors/ElementAssertion'); +const { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } = require('./network/actions'); -const pathSeparator = path.sep +const pathSeparator = path.sep; /** * ## Configuration @@ -110,7 +94,7 @@ const pathSeparator = path.sep * @prop {object} [recordHar] - record HAR and will be saved to `output/har`. See more of [HAR options](https://playwright.dev/docs/api/class-browser#browser-new-context-option-record-har). * @prop {string} [testIdAttribute=data-testid] - locate elements based on the testIdAttribute. See more of [locate by test id](https://playwright.dev/docs/locators#locate-by-test-id). */ -const config = {} +const config = {}; /** * Uses [Playwright](https://github.com/microsoft/playwright) library to run tests inside: @@ -332,34 +316,34 @@ const config = {} */ class Playwright extends Helper { constructor(config) { - super(config) + super(config); - playwright = requireWithFallback('playwright', 'playwright-core') + playwright = requireWithFallback('playwright', 'playwright-core'); // set defaults - this.isRemoteBrowser = false - this.isRunning = false - this.isAuthenticated = false - this.sessionPages = {} - this.activeSessionName = '' - this.isElectron = false - this.isCDPConnection = false - this.electronSessions = [] - this.storageState = null + this.isRemoteBrowser = false; + this.isRunning = false; + this.isAuthenticated = false; + this.sessionPages = {}; + this.activeSessionName = ''; + this.isElectron = false; + this.isCDPConnection = false; + this.electronSessions = []; + this.storageState = null; // for network stuff - this.requests = [] - this.recording = false - this.recordedAtLeastOnce = false + this.requests = []; + this.recording = false; + this.recordedAtLeastOnce = false; // for websocket messages - this.webSocketMessages = [] - this.recordingWebSocketMessages = false - this.recordedWebSocketMessagesAtLeastOnce = false - this.cdpSession = null + this.webSocketMessages = []; + this.recordingWebSocketMessages = false; + this.recordedWebSocketMessagesAtLeastOnce = false; + this.cdpSession = null; // override defaults with config - this._setConfig(config) + this._setConfig(config); } _validateConfig(config) { @@ -386,65 +370,61 @@ class Playwright extends Helper { use: { actionTimeout: 0 }, ignoreHTTPSErrors: false, // Adding it here o that context can be set up to ignore the SSL errors, highlightElement: false, - } + }; - process.env.testIdAttribute = 'data-testid' - config = Object.assign(defaults, config) + process.env.testIdAttribute = 'data-testid'; + config = Object.assign(defaults, config); if (availableBrowsers.indexOf(config.browser) < 0) { - throw new Error( - `Invalid config. Can't use browser "${config.browser}". Accepted values: ${availableBrowsers.join(', ')}`, - ) + throw new Error(`Invalid config. Can't use browser "${config.browser}". Accepted values: ${availableBrowsers.join(', ')}`); } - return config + return config; } _getOptionsForBrowser(config) { if (config[config.browser]) { if (config[config.browser].browserWSEndpoint && config[config.browser].browserWSEndpoint.wsEndpoint) { - config[config.browser].browserWSEndpoint = config[config.browser].browserWSEndpoint.wsEndpoint + config[config.browser].browserWSEndpoint = config[config.browser].browserWSEndpoint.wsEndpoint; } return { ...config[config.browser], wsEndpoint: config[config.browser].browserWSEndpoint, - } + }; } - return {} + return {}; } _setConfig(config) { - this.options = this._validateConfig(config) - setRestartStrategy(this.options) + this.options = this._validateConfig(config); + setRestartStrategy(this.options); this.playwrightOptions = { headless: !this.options.show, ...this._getOptionsForBrowser(config), - } + }; if (this.options.channel && this.options.browser === 'chromium') { - this.playwrightOptions.channel = this.options.channel + this.playwrightOptions.channel = this.options.channel; } if (this.options.video) { // set the video resolution with window size - let size = parseWindowSize(this.options.windowSize) + let size = parseWindowSize(this.options.windowSize); // if the video resolution is passed, set the record resoultion with that resolution if (this.options.recordVideo && this.options.recordVideo.size) { - size = parseWindowSize(this.options.recordVideo.size) + size = parseWindowSize(this.options.recordVideo.size); } - this.options.recordVideo = { size } + this.options.recordVideo = { size }; } if (this.options.recordVideo && !this.options.recordVideo.dir) { - this.options.recordVideo.dir = `${global.output_dir}/videos/` + this.options.recordVideo.dir = `${global.output_dir}/videos/`; } - this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint - this.isElectron = this.options.browser === 'electron' - this.userDataDir = this.playwrightOptions.userDataDir - ? `${this.playwrightOptions.userDataDir}_${Date.now().toString()}` - : undefined - this.isCDPConnection = this.playwrightOptions.cdpConnection - popupStore.defaultAction = this.options.defaultPopupAction + this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint; + this.isElectron = this.options.browser === 'electron'; + this.userDataDir = this.playwrightOptions.userDataDir ? `${this.playwrightOptions.userDataDir}_${Date.now().toString()}` : undefined; + this.isCDPConnection = this.playwrightOptions.cdpConnection; + popupStore.defaultAction = this.options.defaultPopupAction; } static _config() { @@ -458,231 +438,225 @@ class Playwright extends Helper { name: 'url', message: 'Base url of site to be tested', default: 'http://localhost', - when: (answers) => answers.Playwright_browser !== 'electron', + when: answers => answers.Playwright_browser !== 'electron', }, { name: 'show', message: 'Show browser window', default: true, type: 'confirm', - when: (answers) => answers.Playwright_browser !== 'electron', + when: answers => answers.Playwright_browser !== 'electron', }, - ] + ]; } static _checkRequirements() { try { - requireWithFallback('playwright', 'playwright-core') + requireWithFallback('playwright', 'playwright-core'); } catch (e) { - return ['playwright@^1.18'] + return ['playwright@^1.18']; } } async _init() { // register an internal selector engine for reading value property of elements in a selector - if (defaultSelectorEnginesInitialized) return - defaultSelectorEnginesInitialized = true + if (defaultSelectorEnginesInitialized) return; + defaultSelectorEnginesInitialized = true; try { - await playwright.selectors.register('__value', createValueEngine) - await playwright.selectors.register('__disabled', createDisabledEngine) - if (process.env.testIdAttribute) await playwright.selectors.setTestIdAttribute(process.env.testIdAttribute) + await playwright.selectors.register('__value', createValueEngine); + await playwright.selectors.register('__disabled', createDisabledEngine); + if (process.env.testIdAttribute) await playwright.selectors.setTestIdAttribute(process.env.testIdAttribute); } catch (e) { - console.warn(e) + console.warn(e); } } _beforeSuite() { if ((restartsSession() || restartsContext()) && !this.options.manualStart && !this.isRunning) { - this.debugSection('Session', 'Starting singleton browser session') - return this._startBrowser() + this.debugSection('Session', 'Starting singleton browser session'); + return this._startBrowser(); } } async _before(test) { - this.currentRunningTest = test + this.currentRunningTest = test; recorder.retry({ retries: process.env.FAILED_STEP_RETRIES || 3, - when: (err) => { + when: err => { if (!err || typeof err.message !== 'string') { - return false + return false; } // ignore context errors - return err.message.includes('context') + return err.message.includes('context'); }, - }) + }); - if (restartsBrowser() && !this.options.manualStart) await this._startBrowser() - if (!this.isRunning && !this.options.manualStart) await this._startBrowser() + if (restartsBrowser() && !this.options.manualStart) await this._startBrowser(); + if (!this.isRunning && !this.options.manualStart) await this._startBrowser(); - this.isAuthenticated = false + this.isAuthenticated = false; if (this.isElectron) { - this.browserContext = this.browser.context() + this.browserContext = this.browser.context(); } else if (this.playwrightOptions.userDataDir) { - this.browserContext = this.browser + this.browserContext = this.browser; } else { const contextOptions = { ignoreHTTPSErrors: this.options.ignoreHTTPSErrors, acceptDownloads: true, ...this.options.emulate, - } + }; if (this.options.basicAuth) { - contextOptions.httpCredentials = this.options.basicAuth - this.isAuthenticated = true + contextOptions.httpCredentials = this.options.basicAuth; + this.isAuthenticated = true; } - if (this.options.bypassCSP) contextOptions.bypassCSP = this.options.bypassCSP - if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo + if (this.options.bypassCSP) contextOptions.bypassCSP = this.options.bypassCSP; + if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo; if (this.options.recordHar) { - const harExt = this.options.recordHar.content && this.options.recordHar.content === 'attach' ? 'zip' : 'har' - const fileName = `${`${global.output_dir}${path.sep}har${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.${harExt}` - const dir = path.dirname(fileName) - if (!fileExists(dir)) fs.mkdirSync(dir) - this.options.recordHar.path = fileName - this.currentRunningTest.artifacts.har = fileName - contextOptions.recordHar = this.options.recordHar + const harExt = this.options.recordHar.content && this.options.recordHar.content === 'attach' ? 'zip' : 'har'; + const fileName = `${`${global.output_dir}${path.sep}har${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.${harExt}`; + const dir = path.dirname(fileName); + if (!fileExists(dir)) fs.mkdirSync(dir); + this.options.recordHar.path = fileName; + this.currentRunningTest.artifacts.har = fileName; + contextOptions.recordHar = this.options.recordHar; } - if (this.storageState) contextOptions.storageState = this.storageState - if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent - if (this.options.locale) contextOptions.locale = this.options.locale - if (this.options.colorScheme) contextOptions.colorScheme = this.options.colorScheme - this.contextOptions = contextOptions + if (this.storageState) contextOptions.storageState = this.storageState; + if (this.options.userAgent) contextOptions.userAgent = this.options.userAgent; + if (this.options.locale) contextOptions.locale = this.options.locale; + if (this.options.colorScheme) contextOptions.colorScheme = this.options.colorScheme; + this.contextOptions = contextOptions; if (!this.browserContext || !restartsSession()) { - this.browserContext = await this.browser.newContext(this.contextOptions) // Adding the HTTPSError ignore in the context so that we can ignore those errors + this.browserContext = await this.browser.newContext(this.contextOptions); // Adding the HTTPSError ignore in the context so that we can ignore those errors } } - let mainPage + let mainPage; if (this.isElectron) { - mainPage = await this.browser.firstWindow() + mainPage = await this.browser.firstWindow(); } else { try { - const existingPages = await this.browserContext.pages() - mainPage = existingPages[0] || (await this.browserContext.newPage()) + const existingPages = await this.browserContext.pages(); + mainPage = existingPages[0] || (await this.browserContext.newPage()); } catch (e) { if (this.playwrightOptions.userDataDir) { - this.browser = await playwright[this.options.browser].launchPersistentContext( - this.userDataDir, - this.playwrightOptions, - ) - this.browserContext = this.browser - const existingPages = await this.browserContext.pages() - mainPage = existingPages[0] + this.browser = await playwright[this.options.browser].launchPersistentContext(this.userDataDir, this.playwrightOptions); + this.browserContext = this.browser; + const existingPages = await this.browserContext.pages(); + mainPage = existingPages[0]; } } } - await targetCreatedHandler.call(this, mainPage) + await targetCreatedHandler.call(this, mainPage); - await this._setPage(mainPage) + await this._setPage(mainPage); - if (this.options.trace) await this.browserContext.tracing.start({ screenshots: true, snapshots: true }) + if (this.options.trace) await this.browserContext.tracing.start({ screenshots: true, snapshots: true }); - return this.browser + return this.browser; } async _after() { - if (!this.isRunning) return + if (!this.isRunning) return; if (this.isElectron) { - this.browser.close() - this.electronSessions.forEach((session) => session.close()) - return + this.browser.close(); + this.electronSessions.forEach(session => session.close()); + return; } if (restartsSession()) { - return refreshContextSession.bind(this)() + return refreshContextSession.bind(this)(); } if (restartsBrowser()) { - this.isRunning = false - return this._stopBrowser() + this.isRunning = false; + return this._stopBrowser(); } // close other sessions try { if ((await this.browser)._type === 'Browser') { - const contexts = await this.browser.contexts() - const currentContext = contexts[0] + const contexts = await this.browser.contexts(); + const currentContext = contexts[0]; if (currentContext && (this.options.keepCookies || this.options.keepBrowserState)) { - this.storageState = await currentContext.storageState() + this.storageState = await currentContext.storageState(); } - await Promise.all(contexts.map((c) => c.close())) + await Promise.all(contexts.map(c => c.close())); } } catch (e) { - console.log(e) + console.log(e); } // await this.closeOtherTabs(); - return this.browser + return this.browser; } _afterSuite() {} async _finishTest() { - if ((restartsSession() || restartsContext()) && this.isRunning) return this._stopBrowser() + if ((restartsSession() || restartsContext()) && this.isRunning) return this._stopBrowser(); } _session() { - const defaultContext = this.browserContext + const defaultContext = this.browserContext; return { start: async (sessionName = '', config) => { - this.debugSection('New Context', config ? JSON.stringify(config) : 'opened') - this.activeSessionName = sessionName + this.debugSection('New Context', config ? JSON.stringify(config) : 'opened'); + this.activeSessionName = sessionName; - let browserContext - let page + let browserContext; + let page; if (this.isElectron) { - const browser = await playwright._electron.launch(this.playwrightOptions) - this.electronSessions.push(browser) - browserContext = browser.context() - page = await browser.firstWindow() + const browser = await playwright._electron.launch(this.playwrightOptions); + this.electronSessions.push(browser); + browserContext = browser.context(); + page = await browser.firstWindow(); } else { try { - browserContext = await this.browser.newContext(Object.assign(this.contextOptions, config)) - page = await browserContext.newPage() + browserContext = await this.browser.newContext(Object.assign(this.contextOptions, config)); + page = await browserContext.newPage(); } catch (e) { if (this.playwrightOptions.userDataDir) { - browserContext = await playwright[this.options.browser].launchPersistentContext( - `${this.userDataDir}_${this.activeSessionName}`, - this.playwrightOptions, - ) - this.browser = browserContext - page = await browserContext.pages()[0] + browserContext = await playwright[this.options.browser].launchPersistentContext(`${this.userDataDir}_${this.activeSessionName}`, this.playwrightOptions); + this.browser = browserContext; + page = await browserContext.pages()[0]; } } } - if (this.options.trace) await browserContext.tracing.start({ screenshots: true, snapshots: true }) - await targetCreatedHandler.call(this, page) - await this._setPage(page) + if (this.options.trace) await browserContext.tracing.start({ screenshots: true, snapshots: true }); + await targetCreatedHandler.call(this, page); + await this._setPage(page); // Create a new page inside context. - return browserContext + return browserContext; }, stop: async () => { // is closed by _after }, - loadVars: async (context) => { + loadVars: async context => { if (context) { - this.browserContext = context - const existingPages = await context.pages() - this.sessionPages[this.activeSessionName] = existingPages[0] - return this._setPage(this.sessionPages[this.activeSessionName]) + this.browserContext = context; + const existingPages = await context.pages(); + this.sessionPages[this.activeSessionName] = existingPages[0]; + return this._setPage(this.sessionPages[this.activeSessionName]); } }, - restoreVars: async (session) => { - this.withinLocator = null - this.browserContext = defaultContext + restoreVars: async session => { + this.withinLocator = null; + this.browserContext = defaultContext; if (!session) { - this.activeSessionName = '' + this.activeSessionName = ''; } else { - this.activeSessionName = session + this.activeSessionName = session; } - const existingPages = await this.browserContext.pages() - await this._setPage(existingPages[0]) + const existingPages = await this.browserContext.pages(); + await this._setPage(existingPages[0]); - return this._waitForAction() + return this._waitForAction(); }, - } + }; } /** @@ -703,7 +677,7 @@ class Playwright extends Helper { * @param {function} fn async function that executed with Playwright helper as arguments */ usePlaywrightTo(description, fn) { - return this._useTo(...arguments) + return this._useTo(...arguments); } /** @@ -717,7 +691,7 @@ class Playwright extends Helper { * ``` */ amAcceptingPopups() { - popupStore.actionType = 'accept' + popupStore.actionType = 'accept'; } /** @@ -726,7 +700,7 @@ class Playwright extends Helper { * libraries](http://jster.net/category/windows-modals-popups). */ acceptPopup() { - popupStore.assertPopupActionType('accept') + popupStore.assertPopupActionType('accept'); } /** @@ -740,23 +714,23 @@ class Playwright extends Helper { * ``` */ amCancellingPopups() { - popupStore.actionType = 'cancel' + popupStore.actionType = 'cancel'; } /** * Dismisses the active JavaScript popup, as created by window.alert|window.confirm|window.prompt. */ cancelPopup() { - popupStore.assertPopupActionType('cancel') + popupStore.assertPopupActionType('cancel'); } /** * {{> seeInPopup }} */ async seeInPopup(text) { - popupStore.assertPopupVisible() - const popupText = await popupStore.popup.message() - stringIncludes('text in popup').assert(text, popupText) + popupStore.assertPopupVisible(); + const popupText = await popupStore.popup.message(); + stringIncludes('text in popup').assert(text, popupText); } /** @@ -764,21 +738,21 @@ class Playwright extends Helper { * @param {object} page page to set */ async _setPage(page) { - page = await page - this._addPopupListener(page) - this.page = page - if (!page) return - this.browserContext.setDefaultTimeout(0) - page.setDefaultNavigationTimeout(this.options.getPageTimeout) - page.setDefaultTimeout(this.options.timeout) + page = await page; + this._addPopupListener(page); + this.page = page; + if (!page) return; + this.browserContext.setDefaultTimeout(0); + page.setDefaultNavigationTimeout(this.options.getPageTimeout); + page.setDefaultTimeout(this.options.timeout); page.on('crash', async () => { - console.log('ERROR: Page has crashed, closing page!') - await page.close() - }) - this.context = await this.page - this.contextLocator = null - await page.bringToFront() + console.log('ERROR: Page has crashed, closing page!'); + await page.close(); + }); + this.context = await this.page; + this.contextLocator = null; + await page.bringToFront(); } /** @@ -790,33 +764,33 @@ class Playwright extends Helper { */ _addPopupListener(page) { if (!page) { - return + return; } - page.removeAllListeners('dialog') - page.on('dialog', async (dialog) => { - popupStore.popup = dialog - const action = popupStore.actionType || this.options.defaultPopupAction - await this._waitForAction() + page.removeAllListeners('dialog'); + page.on('dialog', async dialog => { + popupStore.popup = dialog; + const action = popupStore.actionType || this.options.defaultPopupAction; + await this._waitForAction(); switch (action) { case 'accept': - return dialog.accept() + return dialog.accept(); case 'cancel': - return dialog.dismiss() + return dialog.dismiss(); default: { - throw new Error('Unknown popup action type. Only "accept" or "cancel" are accepted') + throw new Error('Unknown popup action type. Only "accept" or "cancel" are accepted'); } } - }) + }); } /** * Gets page URL including hash. */ async _getPageUrl() { - return this.executeScript(() => window.location.href) + return this.executeScript(() => window.location.href); } /** @@ -829,48 +803,45 @@ class Playwright extends Helper { */ async grabPopupText() { if (popupStore.popup) { - return popupStore.popup.message() + return popupStore.popup.message(); } - return null + return null; } async _startBrowser() { if (this.isElectron) { - this.browser = await playwright._electron.launch(this.playwrightOptions) + this.browser = await playwright._electron.launch(this.playwrightOptions); } else if (this.isRemoteBrowser && this.isCDPConnection) { try { - this.browser = await playwright[this.options.browser].connectOverCDP(this.playwrightOptions) + this.browser = await playwright[this.options.browser].connectOverCDP(this.playwrightOptions); } catch (err) { if (err.toString().indexOf('ECONNREFUSED')) { - throw new RemoteBrowserConnectionRefused(err) + throw new RemoteBrowserConnectionRefused(err); } - throw err + throw err; } } else if (this.isRemoteBrowser) { try { - this.browser = await playwright[this.options.browser].connect(this.playwrightOptions) + this.browser = await playwright[this.options.browser].connect(this.playwrightOptions); } catch (err) { if (err.toString().indexOf('ECONNREFUSED')) { - throw new RemoteBrowserConnectionRefused(err) + throw new RemoteBrowserConnectionRefused(err); } - throw err + throw err; } } else if (this.playwrightOptions.userDataDir) { - this.browser = await playwright[this.options.browser].launchPersistentContext( - this.userDataDir, - this.playwrightOptions, - ) + this.browser = await playwright[this.options.browser].launchPersistentContext(this.userDataDir, this.playwrightOptions); } else { - this.browser = await playwright[this.options.browser].launch(this.playwrightOptions) + this.browser = await playwright[this.options.browser].launch(this.playwrightOptions); } // works only for Chromium - this.browser.on('targetchanged', (target) => { - this.debugSection('Url', target.url()) - }) + this.browser.on('targetchanged', target => { + this.debugSection('Url', target.url()); + }); - this.isRunning = true - return this.browser + this.isRunning = true; + return this.browser; } /** @@ -879,72 +850,72 @@ class Playwright extends Helper { * @param {object} [contextOptions] See https://playwright.dev/docs/api/class-browser#browser-new-context */ async _createContextPage(contextOptions) { - this.browserContext = await this.browser.newContext(contextOptions) - const page = await this.browserContext.newPage() - targetCreatedHandler.call(this, page) - await this._setPage(page) + this.browserContext = await this.browser.newContext(contextOptions); + const page = await this.browserContext.newPage(); + targetCreatedHandler.call(this, page); + await this._setPage(page); } _getType() { - return this.browser._type + return this.browser._type; } async _stopBrowser() { - this.withinLocator = null - await this._setPage(null) - this.context = null - this.frame = null - popupStore.clear() - if (this.options.recordHar) await this.browserContext.close() - await this.browser.close() + this.withinLocator = null; + await this._setPage(null); + this.context = null; + this.frame = null; + popupStore.clear(); + if (this.options.recordHar) await this.browserContext.close(); + await this.browser.close(); } async _evaluateHandeInContext(...args) { - const context = await this._getContext() - return context.evaluateHandle(...args) + const context = await this._getContext(); + return context.evaluateHandle(...args); } async _withinBegin(locator) { if (this.withinLocator) { - throw new Error("Can't start within block inside another within block") + throw new Error("Can't start within block inside another within block"); } - const frame = isFrameLocator(locator) + const frame = isFrameLocator(locator); if (frame) { if (Array.isArray(frame)) { - await this.switchTo(null) - return frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve()) + await this.switchTo(null); + return frame.reduce((p, frameLocator) => p.then(() => this.switchTo(frameLocator)), Promise.resolve()); } - await this.switchTo(frame) - this.withinLocator = new Locator(frame) - return + await this.switchTo(frame); + this.withinLocator = new Locator(frame); + return; } - const el = await this._locateElement(locator) - assertElementExists(el, locator) - this.context = el - this.contextLocator = locator + const el = await this._locateElement(locator); + assertElementExists(el, locator); + this.context = el; + this.contextLocator = locator; - this.withinLocator = new Locator(locator) + this.withinLocator = new Locator(locator); } async _withinEnd() { - this.withinLocator = null - this.context = await this.page - this.contextLocator = null - this.frame = null + this.withinLocator = null; + this.context = await this.page; + this.contextLocator = null; + this.frame = null; } _extractDataFromPerformanceTiming(timing, ...dataNames) { - const navigationStart = timing.navigationStart + const navigationStart = timing.navigationStart; - const extractedData = {} - dataNames.forEach((name) => { - extractedData[name] = timing[name] - navigationStart - }) + const extractedData = {}; + dataNames.forEach(name => { + extractedData[name] = timing[name] - navigationStart; + }); - return extractedData + return extractedData; } /** @@ -952,32 +923,26 @@ class Playwright extends Helper { */ async amOnPage(url) { if (this.isElectron) { - throw new Error('Cannot open pages inside an Electron container') + throw new Error('Cannot open pages inside an Electron container'); } if (!/^\w+\:(\/\/|.+)/.test(url)) { - url = this.options.url + (url.startsWith('/') ? url : `/${url}`) + url = this.options.url + (url.startsWith('/') ? url : `/${url}`); } if (this.options.basicAuth && this.isAuthenticated !== true) { if (url.includes(this.options.url)) { - await this.browserContext.setHTTPCredentials(this.options.basicAuth) - this.isAuthenticated = true + await this.browserContext.setHTTPCredentials(this.options.basicAuth); + this.isAuthenticated = true; } } - await this.page.goto(url, { waitUntil: this.options.waitForNavigation }) + await this.page.goto(url, { waitUntil: this.options.waitForNavigation }); - const performanceTiming = JSON.parse(await this.page.evaluate(() => JSON.stringify(window.performance.timing))) + const performanceTiming = JSON.parse(await this.page.evaluate(() => JSON.stringify(window.performance.timing))); - perfTiming = this._extractDataFromPerformanceTiming( - performanceTiming, - 'responseEnd', - 'domInteractive', - 'domContentLoadedEventEnd', - 'loadEventEnd', - ) + perfTiming = this._extractDataFromPerformanceTiming(performanceTiming, 'responseEnd', 'domInteractive', 'domContentLoadedEventEnd', 'loadEventEnd'); - return this._waitForAction() + return this._waitForAction(); } /** @@ -998,11 +963,11 @@ class Playwright extends Helper { */ async resizeWindow(width, height) { if (width === 'maximize') { - throw new Error("Playwright can't control windows, so it can't maximize it") + throw new Error("Playwright can't control windows, so it can't maximize it"); } - await this.page.setViewportSize({ width, height }) - return this._waitForAction() + await this.page.setViewportSize({ width, height }); + return this._waitForAction(); } /** @@ -1018,9 +983,9 @@ class Playwright extends Helper { */ async setPlaywrightRequestHeaders(customHeaders) { if (!customHeaders) { - throw new Error('Cannot send empty headers.') + throw new Error('Cannot send empty headers.'); } - return this.browserContext.setExtraHTTPHeaders(customHeaders) + return this.browserContext.setExtraHTTPHeaders(customHeaders); } /** @@ -1028,13 +993,13 @@ class Playwright extends Helper { * */ async moveCursorTo(locator, offsetX = 0, offsetY = 0) { - const el = await this._locateElement(locator) - assertElementExists(el, locator) + const el = await this._locateElement(locator); + assertElementExists(el, locator); // Use manual mouse.move instead of .hover() so the offset can be added to the coordinates - const { x, y } = await clickablePoint(el) - await this.page.mouse.move(x + offsetX, y + offsetY) - return this._waitForAction() + const { x, y } = await clickablePoint(el); + await this.page.mouse.move(x + offsetX, y + offsetY); + return this._waitForAction(); } /** @@ -1042,11 +1007,11 @@ class Playwright extends Helper { * */ async focus(locator, options = {}) { - const el = await this._locateElement(locator) - assertElementExists(el, locator, 'Element to focus') + const el = await this._locateElement(locator); + assertElementExists(el, locator, 'Element to focus'); - await el.focus(options) - return this._waitForAction() + await el.focus(options); + return this._waitForAction(); } /** @@ -1054,11 +1019,11 @@ class Playwright extends Helper { * */ async blur(locator, options = {}) { - const el = await this._locateElement(locator) - assertElementExists(el, locator, 'Element to blur') + const el = await this._locateElement(locator); + assertElementExists(el, locator, 'Element to blur'); - await el.blur(options) - return this._waitForAction() + await el.blur(options); + return this._waitForAction(); } /** * Return the checked status of given element. @@ -1070,14 +1035,14 @@ class Playwright extends Helper { */ async grabCheckedElementStatus(locator, options = {}) { - const supportedTypes = ['checkbox', 'radio'] - const el = await this._locateElement(locator) - const type = await el.getAttribute('type') + const supportedTypes = ['checkbox', 'radio']; + const el = await this._locateElement(locator); + const type = await el.getAttribute('type'); if (supportedTypes.includes(type)) { - return el.isChecked(options) + return el.isChecked(options); } - throw new Error(`Element is not a ${supportedTypes.join(' or ')} input`) + throw new Error(`Element is not a ${supportedTypes.join(' or ')} input`); } /** * Return the disabled status of given element. @@ -1089,8 +1054,8 @@ class Playwright extends Helper { */ async grabDisabledElementStatus(locator, options = {}) { - const el = await this._locateElement(locator) - return el.isDisabled(options) + const el = await this._locateElement(locator); + return el.isDisabled(options); } /** @@ -1107,24 +1072,24 @@ class Playwright extends Helper { * */ async dragAndDrop(srcElement, destElement, options) { - const src = new Locator(srcElement) - const dst = new Locator(destElement) + const src = new Locator(srcElement); + const dst = new Locator(destElement); if (options) { - return this.page.dragAndDrop(buildLocatorString(src), buildLocatorString(dst), options) + return this.page.dragAndDrop(buildLocatorString(src), buildLocatorString(dst), options); } - const _smallWaitInMs = 600 - await this.page.locator(buildLocatorString(src)).hover() - await this.page.mouse.down() - await this.page.waitForTimeout(_smallWaitInMs) + const _smallWaitInMs = 600; + await this.page.locator(buildLocatorString(src)).hover(); + await this.page.mouse.down(); + await this.page.waitForTimeout(_smallWaitInMs); - const destElBox = await this.page.locator(buildLocatorString(dst)).boundingBox() + const destElBox = await this.page.locator(buildLocatorString(dst)).boundingBox(); - await this.page.mouse.move(destElBox.x + destElBox.width / 2, destElBox.y + destElBox.height / 2) - await this.page.locator(buildLocatorString(dst)).hover({ position: { x: 10, y: 10 } }) - await this.page.waitForTimeout(_smallWaitInMs) - await this.page.mouse.up() + await this.page.mouse.move(destElBox.x + destElBox.width / 2, destElBox.y + destElBox.height / 2); + await this.page.locator(buildLocatorString(dst)).hover({ position: { x: 10, y: 10 } }); + await this.page.waitForTimeout(_smallWaitInMs); + await this.page.mouse.up(); } /** @@ -1142,16 +1107,16 @@ class Playwright extends Helper { * @param {object} [contextOptions] [Options for browser context](https://playwright.dev/docs/api/class-browser#browser-new-context) when starting new browser */ async restartBrowser(contextOptions) { - await this._stopBrowser() - await this._startBrowser() - await this._createContextPage(contextOptions) + await this._stopBrowser(); + await this._startBrowser(); + await this._createContextPage(contextOptions); } /** * {{> refreshPage }} */ async refreshPage() { - return this.page.reload({ timeout: this.options.getPageTimeout, waitUntil: this.options.waitForNavigation }) + return this.page.reload({ timeout: this.options.getPageTimeout, waitUntil: this.options.waitForNavigation }); } /** @@ -1172,13 +1137,13 @@ class Playwright extends Helper { * @returns Promise */ async replayFromHar(harFilePath, opts) { - const file = path.join(global.codecept_dir, harFilePath) + const file = path.join(global.codecept_dir, harFilePath); if (!fileExists(file)) { - throw new Error(`File at ${file} cannot be found on local system`) + throw new Error(`File at ${file} cannot be found on local system`); } - await this.page.routeFromHAR(harFilePath, opts) + await this.page.routeFromHAR(harFilePath, opts); } /** @@ -1186,8 +1151,8 @@ class Playwright extends Helper { */ scrollPageToTop() { return this.executeScript(() => { - window.scrollTo(0, 0) - }) + window.scrollTo(0, 0); + }); } /** @@ -1195,13 +1160,10 @@ class Playwright extends Helper { */ async scrollPageToBottom() { return this.executeScript(() => { - const body = document.body - const html = document.documentElement - window.scrollTo( - 0, - Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight), - ) - }) + const body = document.body; + const html = document.documentElement; + window.scrollTo(0, Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight)); + }); } /** @@ -1209,32 +1171,32 @@ class Playwright extends Helper { */ async scrollTo(locator, offsetX = 0, offsetY = 0) { if (typeof locator === 'number' && typeof offsetX === 'number') { - offsetY = offsetX - offsetX = locator - locator = null + offsetY = offsetX; + offsetX = locator; + locator = null; } if (locator) { - const el = await this._locateElement(locator) - assertElementExists(el, locator, 'Element') - await el.scrollIntoViewIfNeeded() - const elementCoordinates = await clickablePoint(el) + const el = await this._locateElement(locator); + assertElementExists(el, locator, 'Element'); + await el.scrollIntoViewIfNeeded(); + const elementCoordinates = await clickablePoint(el); await this.executeScript((offsetX, offsetY) => window.scrollBy(offsetX, offsetY), { offsetX: elementCoordinates.x + offsetX, offsetY: elementCoordinates.y + offsetY, - }) + }); } else { - await this.executeScript(({ offsetX, offsetY }) => window.scrollTo(offsetX, offsetY), { offsetX, offsetY }) + await this.executeScript(({ offsetX, offsetY }) => window.scrollTo(offsetX, offsetY), { offsetX, offsetY }); } - return this._waitForAction() + return this._waitForAction(); } /** * {{> seeInTitle }} */ async seeInTitle(text) { - const title = await this.page.title() - stringIncludes('web page title').assert(text, title) + const title = await this.page.title(); + stringIncludes('web page title').assert(text, title); } /** @@ -1245,33 +1207,33 @@ class Playwright extends Helper { return { x: window.pageXOffset, y: window.pageYOffset, - } + }; } - return this.executeScript(getScrollPosition) + return this.executeScript(getScrollPosition); } /** * {{> seeTitleEquals }} */ async seeTitleEquals(text) { - const title = await this.page.title() - return equals('web page title').assert(title, text) + const title = await this.page.title(); + return equals('web page title').assert(title, text); } /** * {{> dontSeeInTitle }} */ async dontSeeInTitle(text) { - const title = await this.page.title() - stringIncludes('web page title').negate(text, title) + const title = await this.page.title(); + stringIncludes('web page title').negate(text, title); } /** * {{> grabTitle }} */ async grabTitle() { - return this.page.title() + return this.page.title(); } /** @@ -1283,11 +1245,11 @@ class Playwright extends Helper { * ``` */ async _locate(locator) { - const context = await this._getContext() + const context = await this._getContext(); - if (this.frame) return findElements(this.frame, locator) + if (this.frame) return findElements(this.frame, locator); - return findElements(context, locator) + return findElements(context, locator); } /** @@ -1299,8 +1261,8 @@ class Playwright extends Helper { * ``` */ async _locateElement(locator) { - const context = await this._getContext() - return findElement(context, locator) + const context = await this._getContext(); + return findElement(context, locator); } /** @@ -1312,10 +1274,10 @@ class Playwright extends Helper { * ``` */ async _locateCheckable(locator, providedContext = null) { - const context = providedContext || (await this._getContext()) - const els = await findCheckable.call(this, locator, context) - assertElementExists(els[0], locator, 'Checkbox or radio') - return els[0] + const context = providedContext || (await this._getContext()); + const els = await findCheckable.call(this, locator, context); + assertElementExists(els[0], locator, 'Checkbox or radio'); + return els[0]; } /** @@ -1326,8 +1288,8 @@ class Playwright extends Helper { * ``` */ async _locateClickable(locator) { - const context = await this._getContext() - return findClickable.call(this, context, locator) + const context = await this._getContext(); + return findClickable.call(this, context, locator); } /** @@ -1338,7 +1300,7 @@ class Playwright extends Helper { * ``` */ async _locateFields(locator) { - return findFields.call(this, locator) + return findFields.call(this, locator); } /** @@ -1346,7 +1308,7 @@ class Playwright extends Helper { * */ async grabWebElements(locator) { - return this._locate(locator) + return this._locate(locator); } /** @@ -1354,7 +1316,7 @@ class Playwright extends Helper { * */ async grabWebElement(locator) { - return this._locateElement(locator) + return this._locateElement(locator); } /** @@ -1369,20 +1331,20 @@ class Playwright extends Helper { */ async switchToNextTab(num = 1) { if (this.isElectron) { - throw new Error('Cannot switch tabs inside an Electron container') + throw new Error('Cannot switch tabs inside an Electron container'); } - const pages = await this.browserContext.pages() + const pages = await this.browserContext.pages(); - const index = pages.indexOf(this.page) - this.withinLocator = null - const page = pages[index + num] + const index = pages.indexOf(this.page); + this.withinLocator = null; + const page = pages[index + num]; if (!page) { - throw new Error(`There is no ability to switch to next tab with offset ${num}`) + throw new Error(`There is no ability to switch to next tab with offset ${num}`); } - await targetCreatedHandler.call(this, page) - await this._setPage(page) - return this._waitForAction() + await targetCreatedHandler.call(this, page); + await this._setPage(page); + return this._waitForAction(); } /** @@ -1396,19 +1358,19 @@ class Playwright extends Helper { */ async switchToPreviousTab(num = 1) { if (this.isElectron) { - throw new Error('Cannot switch tabs inside an Electron container') + throw new Error('Cannot switch tabs inside an Electron container'); } - const pages = await this.browserContext.pages() - const index = pages.indexOf(this.page) - this.withinLocator = null - const page = pages[index - num] + const pages = await this.browserContext.pages(); + const index = pages.indexOf(this.page); + this.withinLocator = null; + const page = pages[index - num]; if (!page) { - throw new Error(`There is no ability to switch to previous tab with offset ${num}`) + throw new Error(`There is no ability to switch to previous tab with offset ${num}`); } - await this._setPage(page) - return this._waitForAction() + await this._setPage(page); + return this._waitForAction(); } /** @@ -1420,12 +1382,12 @@ class Playwright extends Helper { */ async closeCurrentTab() { if (this.isElectron) { - throw new Error('Cannot close current tab inside an Electron container') + throw new Error('Cannot close current tab inside an Electron container'); } - const oldPage = this.page - await this.switchToPreviousTab() - await oldPage.close() - return this._waitForAction() + const oldPage = this.page; + await this.switchToPreviousTab(); + await oldPage.close(); + return this._waitForAction(); } /** @@ -1436,13 +1398,13 @@ class Playwright extends Helper { * ``` */ async closeOtherTabs() { - const pages = await this.browserContext.pages() - const otherPages = pages.filter((page) => page !== this.page) + const pages = await this.browserContext.pages(); + const otherPages = pages.filter(page => page !== this.page); if (otherPages.length) { - this.debug(`Closing ${otherPages.length} tabs`) - return Promise.all(otherPages.map((p) => p.close())) + this.debug(`Closing ${otherPages.length} tabs`); + return Promise.all(otherPages.map(p => p.close())); } - return Promise.resolve() + return Promise.resolve(); } /** @@ -1461,20 +1423,20 @@ class Playwright extends Helper { */ async openNewTab(options) { if (this.isElectron) { - throw new Error('Cannot open new tabs inside an Electron container') + throw new Error('Cannot open new tabs inside an Electron container'); } - const page = await this.browserContext.newPage(options) - await targetCreatedHandler.call(this, page) - await this._setPage(page) - return this._waitForAction() + const page = await this.browserContext.newPage(options); + await targetCreatedHandler.call(this, page); + await this._setPage(page); + return this._waitForAction(); } /** * {{> grabNumberOfOpenTabs }} */ async grabNumberOfOpenTabs() { - const pages = await this.browserContext.pages() - return pages.length + const pages = await this.browserContext.pages(); + return pages.length; } /** @@ -1482,12 +1444,12 @@ class Playwright extends Helper { * */ async seeElement(locator) { - let els = await this._locate(locator) - els = await Promise.all(els.map((el) => el.isVisible())) + let els = await this._locate(locator); + els = await Promise.all(els.map(el => el.isVisible())); try { - return empty('visible elements').negate(els.filter((v) => v).fill('ELEMENT')) + return empty('visible elements').negate(els.filter(v => v).fill('ELEMENT')); } catch (e) { - dontSeeElementError(locator) + dontSeeElementError(locator); } } @@ -1496,12 +1458,12 @@ class Playwright extends Helper { * */ async dontSeeElement(locator) { - let els = await this._locate(locator) - els = await Promise.all(els.map((el) => el.isVisible())) + let els = await this._locate(locator); + els = await Promise.all(els.map(el => el.isVisible())); try { - return empty('visible elements').assert(els.filter((v) => v).fill('ELEMENT')) + return empty('visible elements').assert(els.filter(v => v).fill('ELEMENT')); } catch (e) { - seeElementError(locator) + seeElementError(locator); } } @@ -1509,11 +1471,11 @@ class Playwright extends Helper { * {{> seeElementInDOM }} */ async seeElementInDOM(locator) { - const els = await this._locate(locator) + const els = await this._locate(locator); try { - return empty('elements on page').negate(els.filter((v) => v).fill('ELEMENT')) + return empty('elements on page').negate(els.filter(v => v).fill('ELEMENT')); } catch (e) { - dontSeeElementInDOMError(locator) + dontSeeElementInDOMError(locator); } } @@ -1521,11 +1483,11 @@ class Playwright extends Helper { * {{> dontSeeElementInDOM }} */ async dontSeeElementInDOM(locator) { - const els = await this._locate(locator) + const els = await this._locate(locator); try { - return empty('elements on a page').assert(els.filter((v) => v).fill('ELEMENT')) + return empty('elements on a page').assert(els.filter(v => v).fill('ELEMENT')); } catch (e) { - seeElementInDOMError(locator) + seeElementInDOMError(locator); } } @@ -1547,19 +1509,19 @@ class Playwright extends Helper { * @return {Promise} */ async handleDownloads(fileName) { - this.page.waitForEvent('download').then(async (download) => { - const filePath = await download.path() - fileName = fileName || `downloads/${path.basename(filePath)}` + this.page.waitForEvent('download').then(async download => { + const filePath = await download.path(); + fileName = fileName || `downloads/${path.basename(filePath)}`; - const downloadPath = path.join(global.output_dir, fileName) + const downloadPath = path.join(global.output_dir, fileName); if (!fs.existsSync(path.dirname(downloadPath))) { - fs.mkdirSync(path.dirname(downloadPath), '0777') + fs.mkdirSync(path.dirname(downloadPath), '0777'); } - fs.copyFileSync(filePath, downloadPath) - this.debug('Download completed') - this.debugSection('Downloaded From', await download.url()) - this.debugSection('Downloaded To', downloadPath) - }) + fs.copyFileSync(filePath, downloadPath); + this.debug('Download completed'); + this.debugSection('Downloaded From', await download.url()); + this.debugSection('Downloaded To', downloadPath); + }); } /** @@ -1579,37 +1541,37 @@ class Playwright extends Helper { * */ async click(locator, context = null, options = {}) { - return proceedClick.call(this, locator, context, options) + return proceedClick.call(this, locator, context, options); } /** * Clicks link and waits for navigation (deprecated) */ async clickLink(locator, context = null) { - console.log('clickLink deprecated: Playwright automatically waits for navigation to happen.') - console.log('Replace I.clickLink with I.click') - return this.click(locator, context) + console.log('clickLink deprecated: Playwright automatically waits for navigation to happen.'); + console.log('Replace I.clickLink with I.click'); + return this.click(locator, context); } /** * {{> forceClick }} */ async forceClick(locator, context = null) { - return proceedClick.call(this, locator, context, { force: true }) + return proceedClick.call(this, locator, context, { force: true }); } /** * {{> doubleClick }} */ async doubleClick(locator, context = null) { - return proceedClick.call(this, locator, context, { clickCount: 2 }) + return proceedClick.call(this, locator, context, { clickCount: 2 }); } /** * {{> rightClick }} */ async rightClick(locator, context = null) { - return proceedClick.call(this, locator, context, { button: 'right' }) + return proceedClick.call(this, locator, context, { button: 'right' }); } /** @@ -1628,9 +1590,9 @@ class Playwright extends Helper { * */ async checkOption(field, context = null, options = { force: true }) { - const elm = await this._locateCheckable(field, context) - await elm.check(options) - return this._waitForAction() + const elm = await this._locateCheckable(field, context); + await elm.check(options); + return this._waitForAction(); } /** @@ -1648,41 +1610,41 @@ class Playwright extends Helper { * {{> uncheckOption }} */ async uncheckOption(field, context = null, options = { force: true }) { - const elm = await this._locateCheckable(field, context) - await elm.uncheck(options) - return this._waitForAction() + const elm = await this._locateCheckable(field, context); + await elm.uncheck(options); + return this._waitForAction(); } /** * {{> seeCheckboxIsChecked }} */ async seeCheckboxIsChecked(field) { - return proceedIsChecked.call(this, 'assert', field) + return proceedIsChecked.call(this, 'assert', field); } /** * {{> dontSeeCheckboxIsChecked }} */ async dontSeeCheckboxIsChecked(field) { - return proceedIsChecked.call(this, 'negate', field) + return proceedIsChecked.call(this, 'negate', field); } /** * {{> pressKeyDown }} */ async pressKeyDown(key) { - key = getNormalizedKey.call(this, key) - await this.page.keyboard.down(key) - return this._waitForAction() + key = getNormalizedKey.call(this, key); + await this.page.keyboard.down(key); + return this._waitForAction(); } /** * {{> pressKeyUp }} */ async pressKeyUp(key) { - key = getNormalizedKey.call(this, key) - await this.page.keyboard.up(key) - return this._waitForAction() + key = getNormalizedKey.call(this, key); + await this.page.keyboard.up(key); + return this._waitForAction(); } /** @@ -1692,28 +1654,28 @@ class Playwright extends Helper { * {{> pressKeyWithKeyNormalization }} */ async pressKey(key) { - const modifiers = [] + const modifiers = []; if (Array.isArray(key)) { for (let k of key) { - k = getNormalizedKey.call(this, k) + k = getNormalizedKey.call(this, k); if (isModifierKey(k)) { - modifiers.push(k) + modifiers.push(k); } else { - key = k - break + key = k; + break; } } } else { - key = getNormalizedKey.call(this, key) + key = getNormalizedKey.call(this, key); } for (const modifier of modifiers) { - await this.page.keyboard.down(modifier) + await this.page.keyboard.down(modifier); } - await this.page.keyboard.press(key) + await this.page.keyboard.press(key); for (const modifier of modifiers) { - await this.page.keyboard.up(modifier) + await this.page.keyboard.up(modifier); } - return this._waitForAction() + return this._waitForAction(); } /** @@ -1721,13 +1683,13 @@ class Playwright extends Helper { */ async type(keys, delay = null) { if (!Array.isArray(keys)) { - keys = keys.toString() - keys = keys.split('') + keys = keys.toString(); + keys = keys.split(''); } for (const key of keys) { - await this.page.keyboard.press(key) - if (delay) await this.wait(delay / 1000) + await this.page.keyboard.press(key); + if (delay) await this.wait(delay / 1000); } } @@ -1736,17 +1698,17 @@ class Playwright extends Helper { * */ async fillField(field, value) { - const els = await findFields.call(this, field) - assertElementExists(els, field, 'Field') - const el = els[0] + const els = await findFields.call(this, field); + assertElementExists(els, field, 'Field'); + const el = els[0]; - await el.clear() + await el.clear(); - await highlightActiveElement.call(this, el) + await highlightActiveElement.call(this, el); - await el.type(value.toString(), { delay: this.options.pressKeyDelay }) + await el.type(value.toString(), { delay: this.options.pressKeyDelay }); - return this._waitForAction() + return this._waitForAction(); } /** @@ -1767,44 +1729,44 @@ class Playwright extends Helper { * @param {any} [options] [Additional options](https://playwright.dev/docs/api/class-locator#locator-clear) for available options object as 2nd argument. */ async clearField(locator, options = {}) { - const els = await findFields.call(this, locator) - assertElementExists(els, locator, 'Field to clear') + const els = await findFields.call(this, locator); + assertElementExists(els, locator, 'Field to clear'); - const el = els[0] + const el = els[0]; - await highlightActiveElement.call(this, el) + await highlightActiveElement.call(this, el); - await el.clear() + await el.clear(); - return this._waitForAction() + return this._waitForAction(); } /** * {{> appendField }} */ async appendField(field, value) { - const els = await findFields.call(this, field) - assertElementExists(els, field, 'Field') - await highlightActiveElement.call(this, els[0]) - await els[0].press('End') - await els[0].type(value.toString(), { delay: this.options.pressKeyDelay }) - return this._waitForAction() + const els = await findFields.call(this, field); + assertElementExists(els, field, 'Field'); + await highlightActiveElement.call(this, els[0]); + await els[0].press('End'); + await els[0].type(value.toString(), { delay: this.options.pressKeyDelay }); + return this._waitForAction(); } /** * {{> seeInField }} */ async seeInField(field, value) { - const _value = typeof value === 'boolean' ? value : value.toString() - return proceedSeeInField.call(this, 'assert', field, _value) + const _value = typeof value === 'boolean' ? value : value.toString(); + return proceedSeeInField.call(this, 'assert', field, _value); } /** * {{> dontSeeInField }} */ async dontSeeInField(field, value) { - const _value = typeof value === 'boolean' ? value : value.toString() - return proceedSeeInField.call(this, 'negate', field, _value) + const _value = typeof value === 'boolean' ? value : value.toString(); + return proceedSeeInField.call(this, 'negate', field, _value); } /** @@ -1812,38 +1774,38 @@ class Playwright extends Helper { * */ async attachFile(locator, pathToFile) { - const file = path.join(global.codecept_dir, pathToFile) + const file = path.join(global.codecept_dir, pathToFile); if (!fileExists(file)) { - throw new Error(`File at ${file} can not be found on local system`) + throw new Error(`File at ${file} can not be found on local system`); } - const els = await findFields.call(this, locator) - assertElementExists(els, locator, 'Field') - await els[0].setInputFiles(file) - return this._waitForAction() + const els = await findFields.call(this, locator); + assertElementExists(els, locator, 'Field'); + await els[0].setInputFiles(file); + return this._waitForAction(); } /** * {{> selectOption }} */ async selectOption(select, option) { - const els = await findFields.call(this, select) - assertElementExists(els, select, 'Selectable field') - const el = els[0] + const els = await findFields.call(this, select); + assertElementExists(els, select, 'Selectable field'); + const el = els[0]; - await highlightActiveElement.call(this, el) - let optionToSelect = '' + await highlightActiveElement.call(this, el); + let optionToSelect = ''; try { - optionToSelect = (await el.locator('option', { hasText: option }).textContent()).trim() + optionToSelect = (await el.locator('option', { hasText: option }).textContent()).trim(); } catch (e) { - optionToSelect = option + optionToSelect = option; } - if (!Array.isArray(option)) option = [optionToSelect] + if (!Array.isArray(option)) option = [optionToSelect]; - await el.selectOption(option) - return this._waitForAction() + await el.selectOption(option); + return this._waitForAction(); } /** @@ -1851,37 +1813,37 @@ class Playwright extends Helper { * */ async grabNumberOfVisibleElements(locator) { - let els = await this._locate(locator) - els = await Promise.all(els.map((el) => el.isVisible())) - return els.filter((v) => v).length + let els = await this._locate(locator); + els = await Promise.all(els.map(el => el.isVisible())); + return els.filter(v => v).length; } /** * {{> seeInCurrentUrl }} */ async seeInCurrentUrl(url) { - stringIncludes('url').assert(url, await this._getPageUrl()) + stringIncludes('url').assert(url, await this._getPageUrl()); } /** * {{> dontSeeInCurrentUrl }} */ async dontSeeInCurrentUrl(url) { - stringIncludes('url').negate(url, await this._getPageUrl()) + stringIncludes('url').negate(url, await this._getPageUrl()); } /** * {{> seeCurrentUrlEquals }} */ async seeCurrentUrlEquals(url) { - urlEquals(this.options.url).assert(url, await this._getPageUrl()) + urlEquals(this.options.url).assert(url, await this._getPageUrl()); } /** * {{> dontSeeCurrentUrlEquals }} */ async dontSeeCurrentUrlEquals(url) { - urlEquals(this.options.url).negate(url, await this._getPageUrl()) + urlEquals(this.options.url).negate(url, await this._getPageUrl()); } /** @@ -1890,14 +1852,14 @@ class Playwright extends Helper { * */ async see(text, context = null) { - return proceedSee.call(this, 'assert', text, context) + return proceedSee.call(this, 'assert', text, context); } /** * {{> seeTextEquals }} */ async seeTextEquals(text, context = null) { - return proceedSee.call(this, 'assert', text, context, true) + return proceedSee.call(this, 'assert', text, context, true); } /** @@ -1906,14 +1868,14 @@ class Playwright extends Helper { * */ async dontSee(text, context = null) { - return proceedSee.call(this, 'negate', text, context) + return proceedSee.call(this, 'negate', text, context); } /** * {{> grabSource }} */ async grabSource() { - return this.page.content() + return this.page.content(); } /** @@ -1928,32 +1890,32 @@ class Playwright extends Helper { * @return {Promise} */ async grabBrowserLogs() { - const logs = consoleLogStore.entries - consoleLogStore.clear() - return logs + const logs = consoleLogStore.entries; + consoleLogStore.clear(); + return logs; } /** * {{> grabCurrentUrl }} */ async grabCurrentUrl() { - return this._getPageUrl() + return this._getPageUrl(); } /** * {{> seeInSource }} */ async seeInSource(text) { - const source = await this.page.content() - stringIncludes('HTML source of a page').assert(text, source) + const source = await this.page.content(); + stringIncludes('HTML source of a page').assert(text, source); } /** * {{> dontSeeInSource }} */ async dontSeeInSource(text) { - const source = await this.page.content() - stringIncludes('HTML source of a page').negate(text, source) + const source = await this.page.content(); + stringIncludes('HTML source of a page').negate(text, source); } /** @@ -1962,10 +1924,8 @@ class Playwright extends Helper { * */ async seeNumberOfElements(locator, num) { - const elements = await this._locate(locator) - return equals( - `expected number of elements (${new Locator(locator)}) is ${num}, but found ${elements.length}`, - ).assert(elements.length, num) + const elements = await this._locate(locator); + return equals(`expected number of elements (${new Locator(locator)}) is ${num}, but found ${elements.length}`).assert(elements.length, num); } /** @@ -1974,11 +1934,8 @@ class Playwright extends Helper { * */ async seeNumberOfVisibleElements(locator, num) { - const res = await this.grabNumberOfVisibleElements(locator) - return equals(`expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`).assert( - res, - num, - ) + const res = await this.grabNumberOfVisibleElements(locator); + return equals(`expected number of visible elements (${new Locator(locator)}) is ${num}, but found ${res}`).assert(res, num); } /** @@ -1986,9 +1943,9 @@ class Playwright extends Helper { */ async setCookie(cookie) { if (Array.isArray(cookie)) { - return this.browserContext.addCookies(cookie) + return this.browserContext.addCookies(cookie); } - return this.browserContext.addCookies([cookie]) + return this.browserContext.addCookies([cookie]); } /** @@ -1996,16 +1953,16 @@ class Playwright extends Helper { * */ async seeCookie(name) { - const cookies = await this.browserContext.cookies() - empty(`cookie ${name} to be set`).negate(cookies.filter((c) => c.name === name)) + const cookies = await this.browserContext.cookies(); + empty(`cookie ${name} to be set`).negate(cookies.filter(c => c.name === name)); } /** * {{> dontSeeCookie }} */ async dontSeeCookie(name) { - const cookies = await this.browserContext.cookies() - empty(`cookie ${name} not to be set`).assert(cookies.filter((c) => c.name === name)) + const cookies = await this.browserContext.cookies(); + empty(`cookie ${name} not to be set`).assert(cookies.filter(c => c.name === name)); } /** @@ -2014,10 +1971,10 @@ class Playwright extends Helper { * {{> grabCookie }} */ async grabCookie(name) { - const cookies = await this.browserContext.cookies() - if (!name) return cookies - const cookie = cookies.filter((c) => c.name === name) - if (cookie[0]) return cookie[0] + const cookies = await this.browserContext.cookies(); + if (!name) return cookies; + const cookie = cookies.filter(c => c.name === name); + if (cookie[0]) return cookie[0]; } /** @@ -2026,8 +1983,8 @@ class Playwright extends Helper { async clearCookie() { // Playwright currently doesn't support to delete a certain cookie // https://github.com/microsoft/playwright/blob/main/docs/src/api/class-browsercontext.md#async-method-browsercontextclearcookies - if (!this.browserContext) return - return this.browserContext.clearCookies() + if (!this.browserContext) return; + return this.browserContext.clearCookies(); } /** @@ -2057,9 +2014,9 @@ class Playwright extends Helper { async executeScript(fn, arg) { if (this.context && this.context.constructor.name === 'FrameLocator') { // switching to iframe context - return this.context.locator(':root').evaluate(fn, arg) + return this.context.locator(':root').evaluate(fn, arg); } - return this.page.evaluate.apply(this.page, [fn, arg]) + return this.page.evaluate.apply(this.page, [fn, arg]); } /** @@ -2068,14 +2025,14 @@ class Playwright extends Helper { * @param {*} locator */ _contextLocator(locator) { - locator = buildLocatorString(new Locator(locator, 'css')) + locator = buildLocatorString(new Locator(locator, 'css')); if (this.contextLocator) { - const contextLocator = buildLocatorString(new Locator(this.contextLocator, 'css')) - locator = `${contextLocator} >> ${locator}` + const contextLocator = buildLocatorString(new Locator(this.contextLocator, 'css')); + locator = `${contextLocator} >> ${locator}`; } - return locator + return locator; } /** @@ -2083,11 +2040,11 @@ class Playwright extends Helper { * */ async grabTextFrom(locator) { - locator = this._contextLocator(locator) - const text = await this.page.textContent(locator) - assertElementExists(text, locator) - this.debugSection('Text', text) - return text + locator = this._contextLocator(locator); + const text = await this.page.textContent(locator); + assertElementExists(text, locator); + this.debugSection('Text', text); + return text; } /** @@ -2095,51 +2052,51 @@ class Playwright extends Helper { * */ async grabTextFromAll(locator) { - const els = await this._locate(locator) - const texts = [] + const els = await this._locate(locator); + const texts = []; for (const el of els) { - texts.push(await await el.innerText()) + texts.push(await await el.innerText()); } - this.debug(`Matched ${els.length} elements`) - return texts + this.debug(`Matched ${els.length} elements`); + return texts; } /** * {{> grabValueFrom }} */ async grabValueFrom(locator) { - const values = await this.grabValueFromAll(locator) - assertElementExists(values, locator) - this.debugSection('Value', values[0]) - return values[0] + const values = await this.grabValueFromAll(locator); + assertElementExists(values, locator); + this.debugSection('Value', values[0]); + return values[0]; } /** * {{> grabValueFromAll }} */ async grabValueFromAll(locator) { - const els = await findFields.call(this, locator) - this.debug(`Matched ${els.length} elements`) - return Promise.all(els.map((el) => el.inputValue())) + const els = await findFields.call(this, locator); + this.debug(`Matched ${els.length} elements`); + return Promise.all(els.map(el => el.inputValue())); } /** * {{> grabHTMLFrom }} */ async grabHTMLFrom(locator) { - const html = await this.grabHTMLFromAll(locator) - assertElementExists(html, locator) - this.debugSection('HTML', html[0]) - return html[0] + const html = await this.grabHTMLFromAll(locator); + assertElementExists(html, locator); + this.debugSection('HTML', html[0]); + return html[0]; } /** * {{> grabHTMLFromAll }} */ async grabHTMLFromAll(locator) { - const els = await this._locate(locator) - this.debug(`Matched ${els.length} elements`) - return Promise.all(els.map((el) => el.innerHTML())) + const els = await this._locate(locator); + this.debug(`Matched ${els.length} elements`); + return Promise.all(els.map(el => el.innerHTML())); } /** @@ -2147,10 +2104,10 @@ class Playwright extends Helper { * */ async grabCssPropertyFrom(locator, cssProperty) { - const cssValues = await this.grabCssPropertyFromAll(locator, cssProperty) - assertElementExists(cssValues, locator) - this.debugSection('CSS', cssValues[0]) - return cssValues[0] + const cssValues = await this.grabCssPropertyFromAll(locator, cssProperty); + assertElementExists(cssValues, locator); + this.debugSection('CSS', cssValues[0]); + return cssValues[0]; } /** @@ -2158,15 +2115,11 @@ class Playwright extends Helper { * */ async grabCssPropertyFromAll(locator, cssProperty) { - const els = await this._locate(locator) - this.debug(`Matched ${els.length} elements`) - const cssValues = await Promise.all( - els.map((el) => - el.evaluate((el, cssProperty) => getComputedStyle(el).getPropertyValue(cssProperty), cssProperty), - ), - ) + const els = await this._locate(locator); + this.debug(`Matched ${els.length} elements`); + const cssValues = await Promise.all(els.map(el => el.evaluate((el, cssProperty) => getComputedStyle(el).getPropertyValue(cssProperty), cssProperty))); - return cssValues + return cssValues; } /** @@ -2174,37 +2127,35 @@ class Playwright extends Helper { * */ async seeCssPropertiesOnElements(locator, cssProperties) { - const res = await this._locate(locator) - assertElementExists(res, locator) + const res = await this._locate(locator); + assertElementExists(res, locator); - const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties) - const elemAmount = res.length - let props = [] + const cssPropertiesCamelCase = convertCssPropertiesToCamelCase(cssProperties); + const elemAmount = res.length; + let props = []; for (const element of res) { for (const prop of Object.keys(cssProperties)) { - const cssProp = await this.grabCssPropertyFrom(locator, prop) + const cssProp = await this.grabCssPropertyFrom(locator, prop); if (isColorProperty(prop)) { - props.push(convertColorToRGBA(cssProp)) + props.push(convertColorToRGBA(cssProp)); } else { - props.push(cssProp) + props.push(cssProp); } } } - const values = Object.keys(cssPropertiesCamelCase).map((key) => cssPropertiesCamelCase[key]) - if (!Array.isArray(props)) props = [props] - let chunked = chunkArray(props, values.length) - chunked = chunked.filter((val) => { + const values = Object.keys(cssPropertiesCamelCase).map(key => cssPropertiesCamelCase[key]); + if (!Array.isArray(props)) props = [props]; + let chunked = chunkArray(props, values.length); + chunked = chunked.filter(val => { for (let i = 0; i < val.length; ++i) { // eslint-disable-next-line eqeqeq - if (val[i] != values[i]) return false + if (val[i] != values[i]) return false; } - return true - }) - return equals( - `all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`, - ).assert(chunked.length, elemAmount) + return true; + }); + return equals(`all elements (${new Locator(locator)}) to have CSS property ${JSON.stringify(cssProperties)}`).assert(chunked.length, elemAmount); } /** @@ -2212,33 +2163,30 @@ class Playwright extends Helper { * */ async seeAttributesOnElements(locator, attributes) { - const res = await this._locate(locator) - assertElementExists(res, locator) - - const elemAmount = res.length - const commands = [] - res.forEach((el) => { - Object.keys(attributes).forEach((prop) => { - commands.push(el.evaluate((el, attr) => el[attr] || el.getAttribute(attr), prop)) - }) - }) - let attrs = await Promise.all(commands) - const values = Object.keys(attributes).map((key) => attributes[key]) - if (!Array.isArray(attrs)) attrs = [attrs] - let chunked = chunkArray(attrs, values.length) - chunked = chunked.filter((val) => { + const res = await this._locate(locator); + assertElementExists(res, locator); + + const elemAmount = res.length; + const commands = []; + res.forEach(el => { + Object.keys(attributes).forEach(prop => { + commands.push(el.evaluate((el, attr) => el[attr] || el.getAttribute(attr), prop)); + }); + }); + let attrs = await Promise.all(commands); + const values = Object.keys(attributes).map(key => attributes[key]); + if (!Array.isArray(attrs)) attrs = [attrs]; + let chunked = chunkArray(attrs, values.length); + chunked = chunked.filter(val => { for (let i = 0; i < val.length; ++i) { // the attribute could be a boolean - if (typeof val[i] === 'boolean') return val[i] === values[i] + if (typeof val[i] === 'boolean') return val[i] === values[i]; // if the attribute doesn't exist, returns false as well - if (!val[i] || !val[i].includes(values[i])) return false + if (!val[i] || !val[i].includes(values[i])) return false; } - return true - }) - return equals(`all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`).assert( - chunked.length, - elemAmount, - ) + return true; + }); + return equals(`all elements (${new Locator(locator)}) to have attributes ${JSON.stringify(attributes)}`).assert(chunked.length, elemAmount); } /** @@ -2246,21 +2194,21 @@ class Playwright extends Helper { * */ async dragSlider(locator, offsetX = 0) { - const src = await this._locateElement(locator) - assertElementExists(src, locator, 'Slider Element') + const src = await this._locateElement(locator); + assertElementExists(src, locator, 'Slider Element'); // Note: Using clickablePoint private api because the .BoundingBox does not take into account iframe offsets! - const sliderSource = await clickablePoint(src) + const sliderSource = await clickablePoint(src); // Drag start point - await this.page.mouse.move(sliderSource.x, sliderSource.y, { steps: 5 }) - await this.page.mouse.down() + await this.page.mouse.move(sliderSource.x, sliderSource.y, { steps: 5 }); + await this.page.mouse.down(); // Drag destination - await this.page.mouse.move(sliderSource.x + offsetX, sliderSource.y, { steps: 5 }) - await this.page.mouse.up() + await this.page.mouse.move(sliderSource.x + offsetX, sliderSource.y, { steps: 5 }); + await this.page.mouse.up(); - return this._waitForAction() + return this._waitForAction(); } /** @@ -2268,10 +2216,10 @@ class Playwright extends Helper { * */ async grabAttributeFrom(locator, attr) { - const attrs = await this.grabAttributeFromAll(locator, attr) - assertElementExists(attrs, locator) - this.debugSection('Attribute', attrs[0]) - return attrs[0] + const attrs = await this.grabAttributeFromAll(locator, attr); + assertElementExists(attrs, locator); + this.debugSection('Attribute', attrs[0]); + return attrs[0]; } /** @@ -2279,15 +2227,15 @@ class Playwright extends Helper { * */ async grabAttributeFromAll(locator, attr) { - const els = await this._locate(locator) - this.debug(`Matched ${els.length} elements`) - const array = [] + const els = await this._locate(locator); + this.debug(`Matched ${els.length} elements`); + const array = []; for (let index = 0; index < els.length; index++) { - array.push(await els[index].getAttribute(attr)) + array.push(await els[index].getAttribute(attr)); } - return array + return array; } /** @@ -2295,43 +2243,43 @@ class Playwright extends Helper { * */ async saveElementScreenshot(locator, fileName) { - const outputFile = screenshotOutputFolder(fileName) + const outputFile = screenshotOutputFolder(fileName); - const res = await this._locateElement(locator) - assertElementExists(res, locator) - const elem = res - this.debug(`Screenshot of ${new Locator(locator)} element has been saved to ${outputFile}`) - return elem.screenshot({ path: outputFile, type: 'png' }) + const res = await this._locateElement(locator); + assertElementExists(res, locator); + const elem = res; + this.debug(`Screenshot of ${new Locator(locator)} element has been saved to ${outputFile}`); + return elem.screenshot({ path: outputFile, type: 'png' }); } /** * {{> saveScreenshot }} */ async saveScreenshot(fileName, fullPage) { - const fullPageOption = fullPage || this.options.fullPageScreenshots - let outputFile = screenshotOutputFolder(fileName) + const fullPageOption = fullPage || this.options.fullPageScreenshots; + let outputFile = screenshotOutputFolder(fileName); - this.debug(`Screenshot is saving to ${outputFile}`) + this.debug(`Screenshot is saving to ${outputFile}`); await this.page.screenshot({ path: outputFile, fullPage: fullPageOption, type: 'png', - }) + }); if (this.activeSessionName) { for (const sessionName in this.sessionPages) { - const activeSessionPage = this.sessionPages[sessionName] - outputFile = screenshotOutputFolder(`${sessionName}_${fileName}`) + const activeSessionPage = this.sessionPages[sessionName]; + outputFile = screenshotOutputFolder(`${sessionName}_${fileName}`); - this.debug(`${sessionName} - Screenshot is saving to ${outputFile}`) + this.debug(`${sessionName} - Screenshot is saving to ${outputFile}`); if (activeSessionPage) { await activeSessionPage.screenshot({ path: outputFile, fullPage: fullPageOption, type: 'png', - }) + }); } } } @@ -2355,23 +2303,21 @@ class Playwright extends Helper { * @returns {Promise} response */ async makeApiRequest(method, url, options) { - method = method.toLowerCase() - const allowedMethods = ['get', 'post', 'patch', 'head', 'fetch', 'delete'] + method = method.toLowerCase(); + const allowedMethods = ['get', 'post', 'patch', 'head', 'fetch', 'delete']; if (!allowedMethods.includes(method)) { - throw new Error( - `Method ${method} is not allowed, use the one from a list ${allowedMethods} or switch to using REST helper`, - ) + throw new Error(`Method ${method} is not allowed, use the one from a list ${allowedMethods} or switch to using REST helper`); } if (url.startsWith('/')) { // local url - url = this.options.url + url - this.debugSection('URL', url) + url = this.options.url + url; + this.debugSection('URL', url); } - const response = await this.page.request[method](url, options) - this.debugSection('Status', response.status()) - this.debugSection('Response', await response.text()) + const response = await this.page.request[method](url, options); + this.debugSection('Status', response.status()); + this.debugSection('Response', await response.text()); // hook to allow JSON response handle this if (this.config.onResponse) { @@ -2380,83 +2326,71 @@ class Playwright extends Helper { status: response.status(), statusText: response.statusText(), headers: response.headers(), - } - this.config.onResponse(axiosResponse) + }; + this.config.onResponse(axiosResponse); } - return response + return response; } async _failed(test) { - await this._withinEnd() + await this._withinEnd(); if (!test.artifacts) { - test.artifacts = {} + test.artifacts = {}; } if (this.options.recordVideo && this.page && this.page.video()) { - test.artifacts.video = saveVideoForPage(this.page, `${test.title}.failed`) + test.artifacts.video = saveVideoForPage(this.page, `${test.title}.failed`); for (const sessionName in this.sessionPages) { - test.artifacts[`video_${sessionName}`] = saveVideoForPage( - this.sessionPages[sessionName], - `${test.title}_${sessionName}.failed`, - ) + test.artifacts[`video_${sessionName}`] = saveVideoForPage(this.sessionPages[sessionName], `${test.title}_${sessionName}.failed`); } } if (this.options.trace) { - test.artifacts.trace = await saveTraceForContext(this.browserContext, `${test.title}.failed`) + test.artifacts.trace = await saveTraceForContext(this.browserContext, `${test.title}.failed`); for (const sessionName in this.sessionPages) { - if (!this.sessionPages[sessionName].context) continue - test.artifacts[`trace_${sessionName}`] = await saveTraceForContext( - this.sessionPages[sessionName].context, - `${test.title}_${sessionName}.failed`, - ) + if (!this.sessionPages[sessionName].context) continue; + test.artifacts[`trace_${sessionName}`] = await saveTraceForContext(this.sessionPages[sessionName].context, `${test.title}_${sessionName}.failed`); } } if (this.options.recordHar) { - test.artifacts.har = this.currentRunningTest.artifacts.har + test.artifacts.har = this.currentRunningTest.artifacts.har; } } async _passed(test) { if (this.options.recordVideo && this.page && this.page.video()) { if (this.options.keepVideoForPassedTests) { - test.artifacts.video = saveVideoForPage(this.page, `${test.title}.passed`) + test.artifacts.video = saveVideoForPage(this.page, `${test.title}.passed`); for (const sessionName of Object.keys(this.sessionPages)) { - test.artifacts[`video_${sessionName}`] = saveVideoForPage( - this.sessionPages[sessionName], - `${test.title}_${sessionName}.passed`, - ) + test.artifacts[`video_${sessionName}`] = saveVideoForPage(this.sessionPages[sessionName], `${test.title}_${sessionName}.passed`); } } else { this.page .video() .delete() - .catch((e) => {}) + .catch(e => {}); } } if (this.options.trace) { if (this.options.keepTraceForPassedTests) { if (this.options.trace) { - test.artifacts.trace = await saveTraceForContext(this.browserContext, `${test.title}.passed`) + test.artifacts.trace = await saveTraceForContext(this.browserContext, `${test.title}.passed`); for (const sessionName in this.sessionPages) { - if (!this.sessionPages[sessionName].context) continue - test.artifacts[`trace_${sessionName}`] = await saveTraceForContext( - this.sessionPages[sessionName].context, - `${test.title}_${sessionName}.passed`, - ) + if (!this.sessionPages[sessionName].context) continue; + test.artifacts[`trace_${sessionName}`] = await saveTraceForContext(this.sessionPages[sessionName].context, `${test.title}_${sessionName}.passed`); } } } else { - await this.browserContext.tracing.stop() + await this.browserContext.tracing.stop(); } } if (this.options.recordHar) { - test.artifacts.har = this.currentRunningTest.artifacts.har + test.artifacts.har = this.currentRunningTest.artifacts.har; } } @@ -2464,99 +2398,90 @@ class Playwright extends Helper { * {{> wait }} */ async wait(sec) { - return new Promise((done) => { - setTimeout(done, sec * 1000) - }) + return new Promise(done => { + setTimeout(done, sec * 1000); + }); } /** * {{> waitForEnabled }} */ async waitForEnabled(locator, sec) { - const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout - locator = new Locator(locator, 'css') + const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; + locator = new Locator(locator, 'css'); - let waiter - const context = await this._getContext() + let waiter; + const context = await this._getContext(); if (!locator.isXPath()) { const valueFn = function ([locator]) { - return Array.from(document.querySelectorAll(locator)).filter((el) => !el.disabled).length > 0 - } - waiter = context.waitForFunction(valueFn, [locator.value], { timeout: waitTimeout }) + return Array.from(document.querySelectorAll(locator)).filter(el => !el.disabled).length > 0; + }; + waiter = context.waitForFunction(valueFn, [locator.value], { timeout: waitTimeout }); } else { const enabledFn = function ([locator, $XPath]) { - eval($XPath) // eslint-disable-line no-eval - return $XPath(null, locator).filter((el) => !el.disabled).length > 0 - } - waiter = context.waitForFunction(enabledFn, [locator.value, $XPath.toString()], { timeout: waitTimeout }) + eval($XPath); // eslint-disable-line no-eval + return $XPath(null, locator).filter(el => !el.disabled).length > 0; + }; + waiter = context.waitForFunction(enabledFn, [locator.value, $XPath.toString()], { timeout: waitTimeout }); } - return waiter.catch((err) => { - throw new Error( - `element (${locator.toString()}) still not enabled after ${waitTimeout / 1000} sec\n${err.message}`, - ) - }) + return waiter.catch(err => { + throw new Error(`element (${locator.toString()}) still not enabled after ${waitTimeout / 1000} sec\n${err.message}`); + }); } /** * {{> waitForDisabled }} */ async waitForDisabled(locator, sec) { - const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout - locator = new Locator(locator, 'css') + const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; + locator = new Locator(locator, 'css'); - let waiter - const context = await this._getContext() + let waiter; + const context = await this._getContext(); if (!locator.isXPath()) { const valueFn = function ([locator]) { - return Array.from(document.querySelectorAll(locator)).filter((el) => el.disabled).length > 0 - } - waiter = context.waitForFunction(valueFn, [locator.value], { timeout: waitTimeout }) + return Array.from(document.querySelectorAll(locator)).filter(el => el.disabled).length > 0; + }; + waiter = context.waitForFunction(valueFn, [locator.value], { timeout: waitTimeout }); } else { const disabledFn = function ([locator, $XPath]) { - eval($XPath) // eslint-disable-line no-eval - return $XPath(null, locator).filter((el) => el.disabled).length > 0 - } - waiter = context.waitForFunction(disabledFn, [locator.value, $XPath.toString()], { timeout: waitTimeout }) + eval($XPath); // eslint-disable-line no-eval + return $XPath(null, locator).filter(el => el.disabled).length > 0; + }; + waiter = context.waitForFunction(disabledFn, [locator.value, $XPath.toString()], { timeout: waitTimeout }); } - return waiter.catch((err) => { - throw new Error( - `element (${locator.toString()}) is still enabled after ${waitTimeout / 1000} sec\n${err.message}`, - ) - }) + return waiter.catch(err => { + throw new Error(`element (${locator.toString()}) is still enabled after ${waitTimeout / 1000} sec\n${err.message}`); + }); } /** * {{> waitForValue }} */ async waitForValue(field, value, sec) { - const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout - const locator = new Locator(field, 'css') - const matcher = await this.context - let waiter - const context = await this._getContext() + const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; + const locator = new Locator(field, 'css'); + const matcher = await this.context; + let waiter; + const context = await this._getContext(); if (!locator.isXPath()) { const valueFn = function ([locator, value]) { - return ( - Array.from(document.querySelectorAll(locator)).filter((el) => (el.value || '').indexOf(value) !== -1).length > - 0 - ) - } - waiter = context.waitForFunction(valueFn, [locator.value, value], { timeout: waitTimeout }) + return Array.from(document.querySelectorAll(locator)).filter(el => (el.value || '').indexOf(value) !== -1).length > 0; + }; + waiter = context.waitForFunction(valueFn, [locator.value, value], { timeout: waitTimeout }); } else { const valueFn = function ([locator, $XPath, value]) { - eval($XPath) // eslint-disable-line no-eval - return $XPath(null, locator).filter((el) => (el.value || '').indexOf(value) !== -1).length > 0 - } + eval($XPath); // eslint-disable-line no-eval + return $XPath(null, locator).filter(el => (el.value || '').indexOf(value) !== -1).length > 0; + }; waiter = context.waitForFunction(valueFn, [locator.value, $XPath.toString(), value], { timeout: waitTimeout, - }) + }); } - return waiter.catch((err) => { - const loc = locator.toString() - throw new Error( - `element (${loc}) is not in DOM or there is no element(${loc}) with value "${value}" after ${waitTimeout / 1000} sec\n${err.message}`, - ) - }) + return waiter.catch(err => { + const loc = locator.toString(); + throw new Error(`element (${loc}) is not in DOM or there is no element(${loc}) with value "${value}" after ${waitTimeout / 1000} sec\n${err.message}`); + }); } /** @@ -2564,44 +2489,40 @@ class Playwright extends Helper { * */ async waitNumberOfVisibleElements(locator, num, sec) { - const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout - locator = new Locator(locator, 'css') + const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; + locator = new Locator(locator, 'css'); - let waiter - const context = await this._getContext() + let waiter; + const context = await this._getContext(); if (locator.isCSS()) { const visibleFn = function ([locator, num]) { - const els = document.querySelectorAll(locator) + const els = document.querySelectorAll(locator); if (!els || els.length === 0) { - return false + return false; } - return Array.prototype.filter.call(els, (el) => el.offsetParent !== null).length === num - } - waiter = context.waitForFunction(visibleFn, [locator.value, num], { timeout: waitTimeout }) + return Array.prototype.filter.call(els, el => el.offsetParent !== null).length === num; + }; + waiter = context.waitForFunction(visibleFn, [locator.value, num], { timeout: waitTimeout }); } else { const visibleFn = function ([locator, $XPath, num]) { - eval($XPath) // eslint-disable-line no-eval - return $XPath(null, locator).filter((el) => el.offsetParent !== null).length === num - } + eval($XPath); // eslint-disable-line no-eval + return $XPath(null, locator).filter(el => el.offsetParent !== null).length === num; + }; waiter = context.waitForFunction(visibleFn, [locator.value, $XPath.toString(), num], { timeout: waitTimeout, - }) + }); } - return waiter.catch((err) => { - throw new Error( - `The number of elements (${locator.toString()}) is not ${num} after ${waitTimeout / 1000} sec\n${err.message}`, - ) - }) + return waiter.catch(err => { + throw new Error(`The number of elements (${locator.toString()}) is not ${num} after ${waitTimeout / 1000} sec\n${err.message}`); + }); } /** * {{> waitForClickable }} */ async waitForClickable(locator, waitTimeout) { - console.log( - 'I.waitForClickable is DEPRECATED: This is no longer needed, Playwright automatically waits for element to be clickable', - ) - console.log('Remove usage of this function') + console.log('I.waitForClickable is DEPRECATED: This is no longer needed, Playwright automatically waits for element to be clickable'); + console.log('Remove usage of this function'); } /** @@ -2609,16 +2530,14 @@ class Playwright extends Helper { * */ async waitForElement(locator, sec) { - const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout - locator = new Locator(locator, 'css') + const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; + locator = new Locator(locator, 'css'); - const context = await this._getContext() + const context = await this._getContext(); try { - await context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'attached' }) + await context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'attached' }); } catch (e) { - throw new Error( - `element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${e.message}`, - ) + throw new Error(`element (${locator.toString()}) still not present on page after ${waitTimeout / 1000} sec\n${e.message}`); } } @@ -2628,28 +2547,28 @@ class Playwright extends Helper { * {{> waitForVisible }} */ async waitForVisible(locator, sec) { - const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout - locator = new Locator(locator, 'css') - const context = await this._getContext() - let count = 0 + const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; + locator = new Locator(locator, 'css'); + const context = await this._getContext(); + let count = 0; // we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented - let waiter + let waiter; if (this.frame) { do { - waiter = await this.frame.locator(buildLocatorString(locator)).first().isVisible() - await this.wait(1) - count += 1000 - if (waiter) break - } while (count <= waitTimeout) + waiter = await this.frame.locator(buildLocatorString(locator)).first().isVisible(); + await this.wait(1); + count += 1000; + if (waiter) break; + } while (count <= waitTimeout); - if (!waiter) throw new Error(`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec.`) + if (!waiter) throw new Error(`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec.`); } try { - await context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'visible' }) + await context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'visible' }); } catch (e) { - throw new Error(`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec\n${e.message}`) + throw new Error(`element (${locator.toString()}) still not visible after ${waitTimeout / 1000} sec\n${e.message}`); } } @@ -2657,29 +2576,29 @@ class Playwright extends Helper { * {{> waitForInvisible }} */ async waitForInvisible(locator, sec) { - const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout - locator = new Locator(locator, 'css') - const context = await this._getContext() - let waiter - let count = 0 + const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; + locator = new Locator(locator, 'css'); + const context = await this._getContext(); + let waiter; + let count = 0; // we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented if (this.frame) { do { - waiter = await this.frame.locator(buildLocatorString(locator)).first().isHidden() - await this.wait(1) - count += 1000 - if (waiter) break - } while (count <= waitTimeout) + waiter = await this.frame.locator(buildLocatorString(locator)).first().isHidden(); + await this.wait(1); + count += 1000; + if (waiter) break; + } while (count <= waitTimeout); - if (!waiter) throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec.`) - return + if (!waiter) throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec.`); + return; } try { - await context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'hidden' }) + await context.locator(buildLocatorString(locator)).first().waitFor({ timeout: waitTimeout, state: 'hidden' }); } catch (e) { - throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec\n${e.message}`) + throw new Error(`element (${locator.toString()}) still visible after ${waitTimeout / 1000} sec\n${e.message}`); } } @@ -2687,136 +2606,134 @@ class Playwright extends Helper { * {{> waitToHide }} */ async waitToHide(locator, sec) { - const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout - locator = new Locator(locator, 'css') - const context = await this._getContext() - let waiter - let count = 0 + const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; + locator = new Locator(locator, 'css'); + const context = await this._getContext(); + let waiter; + let count = 0; // we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented if (this.frame) { do { - waiter = await this.frame.locator(buildLocatorString(locator)).first().isHidden() - await this.wait(1) - count += 1000 - if (waiter) break - } while (count <= waitTimeout) + waiter = await this.frame.locator(buildLocatorString(locator)).first().isHidden(); + await this.wait(1); + count += 1000; + if (waiter) break; + } while (count <= waitTimeout); - if (!waiter) throw new Error(`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec.`) - return + if (!waiter) throw new Error(`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec.`); + return; } return context .locator(buildLocatorString(locator)) .first() .waitFor({ timeout: waitTimeout, state: 'hidden' }) - .catch((err) => { - throw new Error( - `element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`, - ) - }) + .catch(err => { + throw new Error(`element (${locator.toString()}) still not hidden after ${waitTimeout / 1000} sec\n${err.message}`); + }); } /** * {{> waitForNumberOfTabs }} */ async waitForNumberOfTabs(expectedTabs, sec) { - const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout - let currentTabs - let count = 0 + const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; + let currentTabs; + let count = 0; do { - currentTabs = await this.grabNumberOfOpenTabs() - await this.wait(1) - count += 1000 - if (currentTabs >= expectedTabs) return - } while (count <= waitTimeout) + currentTabs = await this.grabNumberOfOpenTabs(); + await this.wait(1); + count += 1000; + if (currentTabs >= expectedTabs) return; + } while (count <= waitTimeout); - throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`) + throw new Error(`Expected ${expectedTabs} tabs are not met after ${waitTimeout / 1000} sec.`); } async _getContext() { if ((this.context && this.context.constructor.name === 'FrameLocator') || this.context) { - return this.context + return this.context; } - return this.page + return this.page; } /** * {{> waitInUrl }} */ async waitInUrl(urlPart, sec = null) { - const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout + const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; return this.page .waitForFunction( - (urlPart) => { - const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href))) - return currUrl.indexOf(urlPart) > -1 + urlPart => { + const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href))); + return currUrl.indexOf(urlPart) > -1; }, urlPart, { timeout: waitTimeout }, ) - .catch(async (e) => { - const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data. + .catch(async e => { + const currUrl = await this._getPageUrl(); // Required because the waitForFunction can't return data. if (/Timeout/i.test(e.message)) { - throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`) + throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`); } else { - throw e + throw e; } - }) + }); } /** * {{> waitUrlEquals }} */ async waitUrlEquals(urlPart, sec = null) { - const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout + const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; - const baseUrl = this.options.url + const baseUrl = this.options.url; if (urlPart.indexOf('http') < 0) { - urlPart = baseUrl + urlPart + urlPart = baseUrl + urlPart; } return this.page .waitForFunction( - (urlPart) => { - const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href))) - return currUrl.indexOf(urlPart) > -1 + urlPart => { + const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href))); + return currUrl.indexOf(urlPart) > -1; }, urlPart, { timeout: waitTimeout }, ) - .catch(async (e) => { - const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data. + .catch(async e => { + const currUrl = await this._getPageUrl(); // Required because the waitForFunction can't return data. if (/Timeout/i.test(e.message)) { - throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`) + throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`); } else { - throw e + throw e; } - }) + }); } /** * {{> waitForText }} */ async waitForText(text, sec = null, context = null) { - const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout - const errorMessage = `Text "${text}" was not found on page after ${waitTimeout / 1000} sec.` - let waiter + const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; + const errorMessage = `Text "${text}" was not found on page after ${waitTimeout / 1000} sec.`; + let waiter; - const contextObject = await this._getContext() + const contextObject = await this._getContext(); if (context) { - const locator = new Locator(context, 'css') + const locator = new Locator(context, 'css'); if (!locator.isXPath()) { try { await contextObject .locator(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()} >> text=${text}`) .first() - .waitFor({ timeout: waitTimeout, state: 'visible' }) + .waitFor({ timeout: waitTimeout, state: 'visible' }); } catch (e) { - throw new Error(`${errorMessage}\n${e.message}`) + throw new Error(`${errorMessage}\n${e.message}`); } } @@ -2824,34 +2741,34 @@ class Playwright extends Helper { try { await contextObject.waitForFunction( ([locator, text, $XPath]) => { - eval($XPath) // eslint-disable-line no-eval - const el = $XPath(null, locator) - if (!el.length) return false - return el[0].innerText.indexOf(text) > -1 + eval($XPath); // eslint-disable-line no-eval + const el = $XPath(null, locator); + if (!el.length) return false; + return el[0].innerText.indexOf(text) > -1; }, [locator.value, text, $XPath.toString()], { timeout: waitTimeout }, - ) + ); } catch (e) { - throw new Error(`${errorMessage}\n${e.message}`) + throw new Error(`${errorMessage}\n${e.message}`); } } } else { // we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented - const _contextObject = this.frame ? this.frame : contextObject - let count = 0 + const _contextObject = this.frame ? this.frame : contextObject; + let count = 0; do { waiter = await _contextObject .locator(`:has-text(${JSON.stringify(text)})`) .first() - .isVisible() - if (waiter) break - await this.wait(1) - count += 1000 - } while (count <= waitTimeout) + .isVisible(); + if (waiter) break; + await this.wait(1); + count += 1000; + } while (count <= waitTimeout); - if (!waiter) throw new Error(`${errorMessage}`) + if (!waiter) throw new Error(`${errorMessage}`); } } @@ -2867,8 +2784,8 @@ class Playwright extends Helper { * @param {?number} [sec=null] seconds to wait */ async waitForRequest(urlOrPredicate, sec = null) { - const timeout = sec ? sec * 1000 : this.options.waitForTimeout - return this.page.waitForRequest(urlOrPredicate, { timeout }) + const timeout = sec ? sec * 1000 : this.options.waitForTimeout; + return this.page.waitForRequest(urlOrPredicate, { timeout }); } /** @@ -2883,8 +2800,8 @@ class Playwright extends Helper { * @param {?number} [sec=null] number of seconds to wait */ async waitForResponse(urlOrPredicate, sec = null) { - const timeout = sec ? sec * 1000 : this.options.waitForTimeout - return this.page.waitForResponse(urlOrPredicate, { timeout }) + const timeout = sec ? sec * 1000 : this.options.waitForTimeout; + return this.page.waitForResponse(urlOrPredicate, { timeout }); } /** @@ -2894,51 +2811,51 @@ class Playwright extends Helper { if (Number.isInteger(locator)) { // Select by frame index of current context - let childFrames = null + let childFrames = null; if (this.context && typeof this.context.childFrames === 'function') { - childFrames = this.context.childFrames() + childFrames = this.context.childFrames(); } else { - childFrames = this.page.mainFrame().childFrames() + childFrames = this.page.mainFrame().childFrames(); } if (locator >= 0 && locator < childFrames.length) { - this.context = await this.page.frameLocator('iframe').nth(locator) - this.contextLocator = locator + this.context = await this.page.frameLocator('iframe').nth(locator); + this.contextLocator = locator; } else { - throw new Error('Element #invalidIframeSelector was not found by text|CSS|XPath') + throw new Error('Element #invalidIframeSelector was not found by text|CSS|XPath'); } - return + return; } if (!locator) { - this.context = this.page - this.contextLocator = null - this.frame = null - return + this.context = this.page; + this.contextLocator = null; + this.frame = null; + return; } // iframe by selector - locator = buildLocatorString(new Locator(locator, 'css')) - const frame = await this._locateElement(locator) + locator = buildLocatorString(new Locator(locator, 'css')); + const frame = await this._locateElement(locator); if (!frame) { - throw new Error(`Frame ${JSON.stringify(locator)} was not found by text|CSS|XPath`) + throw new Error(`Frame ${JSON.stringify(locator)} was not found by text|CSS|XPath`); } if (this.frame) { - this.frame = await this.frame.frameLocator(locator) + this.frame = await this.frame.frameLocator(locator); } else { - this.frame = await this.page.frameLocator(locator) + this.frame = await this.page.frameLocator(locator); } - const contentFrame = this.frame + const contentFrame = this.frame; if (contentFrame) { - this.context = contentFrame - this.contextLocator = null + this.context = contentFrame; + this.contextLocator = null; } else { - this.context = this.page.frame(this.page.frames()[1].name()) - this.contextLocator = locator + this.context = this.page.frame(this.page.frames()[1].name()); + this.contextLocator = locator; } } @@ -2946,17 +2863,17 @@ class Playwright extends Helper { * {{> waitForFunction }} */ async waitForFunction(fn, argsOrSec = null, sec = null) { - let args = [] + let args = []; if (argsOrSec) { if (Array.isArray(argsOrSec)) { - args = argsOrSec + args = argsOrSec; } else if (typeof argsOrSec === 'number') { - sec = argsOrSec + sec = argsOrSec; } } - const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout - const context = await this._getContext() - return context.waitForFunction(fn, args, { timeout: waitTimeout }) + const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; + const context = await this._getContext(); + return context.waitForFunction(fn, args, { timeout: waitTimeout }); } /** @@ -2968,13 +2885,13 @@ class Playwright extends Helper { */ async waitForNavigation(options = {}) { console.log(`waitForNavigation deprecated: - * This method is inherently racy, please use 'waitForURL' instead.`) + * This method is inherently racy, please use 'waitForURL' instead.`); options = { timeout: this.options.getPageTimeout, waitUntil: this.options.waitForNavigation, ...options, - } - return this.page.waitForNavigation(options) + }; + return this.page.waitForNavigation(options); } /** @@ -2990,44 +2907,44 @@ class Playwright extends Helper { timeout: this.options.getPageTimeout, waitUntil: this.options.waitForNavigation, ...options, - } - return this.page.waitForURL(url, options) + }; + return this.page.waitForURL(url, options); } async waitUntilExists(locator, sec) { console.log(`waitUntilExists deprecated: * use 'waitForElement' to wait for element to be attached - * use 'waitForDetached to wait for element to be removed'`) - return this.waitForDetached(locator, sec) + * use 'waitForDetached to wait for element to be removed'`); + return this.waitForDetached(locator, sec); } /** * {{> waitForDetached }} */ async waitForDetached(locator, sec) { - const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout - locator = new Locator(locator, 'css') + const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; + locator = new Locator(locator, 'css'); - let waiter - const context = await this._getContext() + let waiter; + const context = await this._getContext(); if (!locator.isXPath()) { try { await context .locator(`${locator.isCustom() ? `${locator.type}=${locator.value}` : locator.simplify()}`) .first() - .waitFor({ timeout: waitTimeout, state: 'detached' }) + .waitFor({ timeout: waitTimeout, state: 'detached' }); } catch (e) { - throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${e.message}`) + throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${e.message}`); } } else { const visibleFn = function ([locator, $XPath]) { - eval($XPath) // eslint-disable-line no-eval - return $XPath(null, locator).length === 0 - } - waiter = context.waitForFunction(visibleFn, [locator.value, $XPath.toString()], { timeout: waitTimeout }) - return waiter.catch((err) => { - throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${err.message}`) - }) + eval($XPath); // eslint-disable-line no-eval + return $XPath(null, locator).length === 0; + }; + waiter = context.waitForFunction(visibleFn, [locator.value, $XPath.toString()], { timeout: waitTimeout }); + return waiter.catch(err => { + throw new Error(`element (${locator.toString()}) still on page after ${waitTimeout / 1000} sec\n${err.message}`); + }); } } @@ -3036,56 +2953,56 @@ class Playwright extends Helper { */ async waitForCookie(name, sec) { // by default, we will retry 3 times - let retries = 3 - const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout + let retries = 3; + const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout; if (sec) { - retries = sec + retries = sec; } else { - retries = Math.ceil(waitTimeout / 1000) - 1 + retries = Math.ceil(waitTimeout / 1000) - 1; } return promiseRetry( async (retry, number) => { - const _grabCookie = async (name) => { - const cookies = await this.browserContext.cookies() - const cookie = cookies.filter((c) => c.name === name) - if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`) - } + const _grabCookie = async name => { + const cookies = await this.browserContext.cookies(); + const cookie = cookies.filter(c => c.name === name); + if (cookie.length === 0) throw Error(`Cookie ${name} is not found after ${retries}s`); + }; - this.debugSection('Wait for cookie: ', name) - if (number > 1) this.debugSection('Retrying... Attempt #', number) + this.debugSection('Wait for cookie: ', name); + if (number > 1) this.debugSection('Retrying... Attempt #', number); try { - await _grabCookie(name) + await _grabCookie(name); } catch (e) { - retry(e) + retry(e); } }, { retries, maxTimeout: 1000 }, - ) + ); } async _waitForAction() { - return this.wait(this.options.waitForAction / 1000) + return this.wait(this.options.waitForAction / 1000); } /** * {{> grabDataFromPerformanceTiming }} */ async grabDataFromPerformanceTiming() { - return perfTiming + return perfTiming; } /** * {{> grabElementBoundingRect }} */ async grabElementBoundingRect(locator, prop) { - const el = await this._locateElement(locator) - assertElementExists(el, locator) - const rect = await el.boundingBox() - if (prop) return rect[prop] - return rect + const el = await this._locateElement(locator); + assertElementExists(el, locator); + const rect = await el.boundingBox(); + if (prop) return rect[prop]; + return rect; } /** @@ -3100,7 +3017,7 @@ class Playwright extends Helper { * @param {function} [handler] a function to process request */ async mockRoute(url, handler) { - return this.browserContext.route(...arguments) + return this.browserContext.route(...arguments); } /** @@ -3116,7 +3033,7 @@ class Playwright extends Helper { * @param {function} [handler] a function to process request */ async stopMockingRoute(url, handler) { - return this.browserContext.unroute(...arguments) + return this.browserContext.unroute(...arguments); } /** @@ -3124,27 +3041,27 @@ class Playwright extends Helper { * */ startRecordingTraffic() { - this.flushNetworkTraffics() - this.recording = true - this.recordedAtLeastOnce = true + this.flushNetworkTraffics(); + this.recording = true; + this.recordedAtLeastOnce = true; - this.page.on('requestfinished', async (request) => { + this.page.on('requestfinished', async request => { const information = { url: request.url(), method: request.method(), requestHeaders: request.headers(), requestPostData: request.postData(), response: request.response(), - } + }; - this.debugSection('REQUEST: ', JSON.stringify(information)) + this.debugSection('REQUEST: ', JSON.stringify(information)); if (typeof information.requestPostData === 'object') { - information.requestPostData = JSON.parse(information.requestPostData) + information.requestPostData = JSON.parse(information.requestPostData); } - this.requests.push(information) - }) + this.requests.push(information); + }); } /** @@ -3167,21 +3084,21 @@ class Playwright extends Helper { */ blockTraffic(urls) { if (Array.isArray(urls)) { - urls.forEach((url) => { - this.page.route(url, (route) => { + urls.forEach(url => { + this.page.route(url, route => { route .abort() // Sometimes it happens that browser has been closed in the meantime. It is ok to ignore error then. - .catch((e) => {}) - }) - }) + .catch(e => {}); + }); + }); } else { - this.page.route(urls, (route) => { + this.page.route(urls, route => { route .abort() // Sometimes it happens that browser has been closed in the meantime. It is ok to ignore error then. - .catch((e) => {}) - }) + .catch(e => {}); + }); } } @@ -3203,26 +3120,26 @@ class Playwright extends Helper { */ mockTraffic(urls, responseString, contentType = 'application/json') { // Required to mock cross-domain requests - const headers = { 'access-control-allow-origin': '*' } + const headers = { 'access-control-allow-origin': '*' }; if (typeof urls === 'string') { - urls = [urls] + urls = [urls]; } - urls.forEach((url) => { - this.page.route(url, (route) => { + urls.forEach(url => { + this.page.route(url, route => { if (this.page.isClosed()) { // Sometimes it happens that browser has been closed in the meantime. // In this case we just don't fulfill to prevent error in test scenario. - return + return; } route.fulfill({ contentType, headers, body: responseString, - }) - }) - }) + }); + }); + }); } /** @@ -3230,7 +3147,7 @@ class Playwright extends Helper { * {{> flushNetworkTraffics }} */ flushNetworkTraffics() { - flushNetworkTraffics.call(this) + flushNetworkTraffics.call(this); } /** @@ -3238,7 +3155,7 @@ class Playwright extends Helper { * {{> stopRecordingTraffic }} */ stopRecordingTraffic() { - stopRecordingTraffic.call(this) + stopRecordingTraffic.call(this); } /** @@ -3256,23 +3173,21 @@ class Playwright extends Helper { */ grabTrafficUrl(urlMatch) { if (!this.recordedAtLeastOnce) { - throw new Error( - 'Failure in test automation. You use "I.grabTrafficUrl", but "I.startRecordingTraffic" was never called before.', - ) + throw new Error('Failure in test automation. You use "I.grabTrafficUrl", but "I.startRecordingTraffic" was never called before.'); } for (const i in this.requests) { // eslint-disable-next-line no-prototype-builtins if (this.requests.hasOwnProperty(i)) { - const request = this.requests[i] + const request = this.requests[i]; if (request.url && request.url.match(new RegExp(urlMatch))) { - return request.url + return request.url; } } } - assert.fail(`Method "getTrafficUrl" failed: No request found in traffic that matches ${urlMatch}`) + assert.fail(`Method "getTrafficUrl" failed: No request found in traffic that matches ${urlMatch}`); } /** @@ -3280,7 +3195,7 @@ class Playwright extends Helper { * {{> grabRecordedNetworkTraffics }} */ async grabRecordedNetworkTraffics() { - return grabRecordedNetworkTraffics.call(this) + return grabRecordedNetworkTraffics.call(this); } /** @@ -3288,7 +3203,7 @@ class Playwright extends Helper { * {{> seeTraffic }} */ async seeTraffic({ name, url, parameters, requestPostData, timeout = 10 }) { - await seeTraffic.call(this, ...arguments) + await seeTraffic.call(this, ...arguments); } /** @@ -3297,42 +3212,42 @@ class Playwright extends Helper { * */ dontSeeTraffic({ name, url }) { - dontSeeTraffic.call(this, ...arguments) + dontSeeTraffic.call(this, ...arguments); } /** * {{> startRecordingWebSocketMessages }} */ async startRecordingWebSocketMessages() { - this.flushWebSocketMessages() - this.recordingWebSocketMessages = true - this.recordedWebSocketMessagesAtLeastOnce = true + this.flushWebSocketMessages(); + this.recordingWebSocketMessages = true; + this.recordedWebSocketMessagesAtLeastOnce = true; - this.cdpSession = await this.getNewCDPSession() - await this.cdpSession.send('Network.enable') - await this.cdpSession.send('Page.enable') + this.cdpSession = await this.getNewCDPSession(); + await this.cdpSession.send('Network.enable'); + await this.cdpSession.send('Page.enable'); - this.cdpSession.on('Network.webSocketFrameReceived', (payload) => { - this._logWebsocketMessages(this._getWebSocketLog('RECEIVED', payload)) - }) + this.cdpSession.on('Network.webSocketFrameReceived', payload => { + this._logWebsocketMessages(this._getWebSocketLog('RECEIVED', payload)); + }); - this.cdpSession.on('Network.webSocketFrameSent', (payload) => { - this._logWebsocketMessages(this._getWebSocketLog('SENT', payload)) - }) + this.cdpSession.on('Network.webSocketFrameSent', payload => { + this._logWebsocketMessages(this._getWebSocketLog('SENT', payload)); + }); - this.cdpSession.on('Network.webSocketFrameError', (payload) => { - this._logWebsocketMessages(this._getWebSocketLog('ERROR', payload)) - }) + this.cdpSession.on('Network.webSocketFrameError', payload => { + this._logWebsocketMessages(this._getWebSocketLog('ERROR', payload)); + }); } /** * {{> stopRecordingWebSocketMessages }} */ async stopRecordingWebSocketMessages() { - await this.cdpSession.send('Network.disable') - await this.cdpSession.send('Page.disable') - this.page.removeAllListeners('Network') - this.recordingWebSocketMessages = false + await this.cdpSession.send('Network.disable'); + await this.cdpSession.send('Page.disable'); + this.page.removeAllListeners('Network'); + this.recordingWebSocketMessages = false; } /** @@ -3344,19 +3259,17 @@ class Playwright extends Helper { grabWebSocketMessages() { if (!this.recordingWebSocketMessages) { if (!this.recordedWebSocketMessagesAtLeastOnce) { - throw new Error( - 'Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.', - ) + throw new Error('Failure in test automation. You use "I.grabWebSocketMessages", but "I.startRecordingWebSocketMessages" was never called before.'); } } - return this.webSocketMessages + return this.webSocketMessages; } /** * Resets all recorded WS messages. */ flushWebSocketMessages() { - this.webSocketMessages = [] + this.webSocketMessages = []; } /** @@ -3414,350 +3327,344 @@ class Playwright extends Helper { * @return {Promise>} */ async grabMetrics() { - const client = await this.page.context().newCDPSession(this.page) - await client.send('Performance.enable') - const perfMetricObject = await client.send('Performance.getMetrics') - return perfMetricObject?.metrics + const client = await this.page.context().newCDPSession(this.page); + await client.send('Performance.enable'); + const perfMetricObject = await client.send('Performance.getMetrics'); + return perfMetricObject?.metrics; } _getWebSocketMessage(payload) { if (payload.errorMessage) { - return payload.errorMessage + return payload.errorMessage; } - return payload.response.payloadData + return payload.response.payloadData; } _getWebSocketLog(prefix, payload) { - return `${prefix} ID: ${payload.requestId} TIMESTAMP: ${payload.timestamp} (${new Date().toISOString()})\n\n${this._getWebSocketMessage(payload)}\n\n` + return `${prefix} ID: ${payload.requestId} TIMESTAMP: ${payload.timestamp} (${new Date().toISOString()})\n\n${this._getWebSocketMessage(payload)}\n\n`; } async getNewCDPSession() { - return this.page.context().newCDPSession(this.page) + return this.page.context().newCDPSession(this.page); } _logWebsocketMessages(message) { - this.webSocketMessages.push(message) + this.webSocketMessages.push(message); } } -module.exports = Playwright +module.exports = Playwright; function buildLocatorString(locator) { if (locator.isCustom()) { - return `${locator.type}=${locator.value}` + return `${locator.type}=${locator.value}`; } if (locator.isXPath()) { - return `xpath=${locator.value}` + return `xpath=${locator.value}`; } - return locator.simplify() + return locator.simplify(); } async function findElements(matcher, locator) { - if (locator.react) return findReact(matcher, locator) - if (locator.vue) return findVue(matcher, locator) - if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator) - locator = new Locator(locator, 'css') + if (locator.react) return findReact(matcher, locator); + if (locator.vue) return findVue(matcher, locator); + if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator); + locator = new Locator(locator, 'css'); - return matcher.locator(buildLocatorString(locator)).all() + return matcher.locator(buildLocatorString(locator)).all(); } async function findElement(matcher, locator) { - if (locator.react) return findReact(matcher, locator) - if (locator.vue) return findVue(matcher, locator) - if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator) - locator = new Locator(locator, 'css') + if (locator.react) return findReact(matcher, locator); + if (locator.vue) return findVue(matcher, locator); + if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator); + locator = new Locator(locator, 'css'); - return matcher.locator(buildLocatorString(locator)).first() + return matcher.locator(buildLocatorString(locator)).first(); } async function getVisibleElements(elements) { - const visibleElements = [] + const visibleElements = []; for (const element of elements) { if (await element.isVisible()) { - visibleElements.push(element) + visibleElements.push(element); } } if (visibleElements.length === 0) { - return elements + return elements; } - return visibleElements + return visibleElements; } async function proceedClick(locator, context = null, options = {}) { - let matcher = await this._getContext() + let matcher = await this._getContext(); if (context) { - const els = await this._locate(context) - assertElementExists(els, context) - matcher = els[0] + const els = await this._locate(context); + assertElementExists(els, context); + matcher = els[0]; } - const els = await findClickable.call(this, matcher, locator) + const els = await findClickable.call(this, matcher, locator); if (context) { - assertElementExists( - els, - locator, - 'Clickable element', - `was not found inside element ${new Locator(context).toString()}`, - ) + assertElementExists(els, locator, 'Clickable element', `was not found inside element ${new Locator(context).toString()}`); } else { - assertElementExists(els, locator, 'Clickable element') + assertElementExists(els, locator, 'Clickable element'); } - await highlightActiveElement.call(this, els[0]) + await highlightActiveElement.call(this, els[0]); /* using the force true options itself but instead dispatching a click */ if (options.force) { - await els[0].dispatchEvent('click') + await els[0].dispatchEvent('click'); } else { - const element = els.length > 1 ? (await getVisibleElements(els))[0] : els[0] - await element.click(options) + const element = els.length > 1 ? (await getVisibleElements(els))[0] : els[0]; + await element.click(options); } - const promises = [] + const promises = []; if (options.waitForNavigation) { - promises.push(this.waitForURL(/.*/, { waitUntil: options.waitForNavigation })) + promises.push(this.waitForURL(/.*/, { waitUntil: options.waitForNavigation })); } - promises.push(this._waitForAction()) + promises.push(this._waitForAction()); - return Promise.all(promises) + return Promise.all(promises); } async function findClickable(matcher, locator) { - if (locator.react) return findReact(matcher, locator) - if (locator.vue) return findVue(matcher, locator) - if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator) + if (locator.react) return findReact(matcher, locator); + if (locator.vue) return findVue(matcher, locator); + if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator); - locator = new Locator(locator) - if (!locator.isFuzzy()) return findElements.call(this, matcher, locator) + locator = new Locator(locator); + if (!locator.isFuzzy()) return findElements.call(this, matcher, locator); - let els - const literal = xpathLocator.literal(locator.value) + let els; + const literal = xpathLocator.literal(locator.value); - els = await findElements.call(this, matcher, Locator.clickable.narrow(literal)) - if (els.length) return els + els = await findElements.call(this, matcher, Locator.clickable.narrow(literal)); + if (els.length) return els; - els = await findElements.call(this, matcher, Locator.clickable.wide(literal)) - if (els.length) return els + els = await findElements.call(this, matcher, Locator.clickable.wide(literal)); + if (els.length) return els; try { - els = await findElements.call(this, matcher, Locator.clickable.self(literal)) - if (els.length) return els + els = await findElements.call(this, matcher, Locator.clickable.self(literal)); + if (els.length) return els; } catch (err) { // Do nothing } - return findElements.call(this, matcher, locator.value) // by css or xpath + return findElements.call(this, matcher, locator.value); // by css or xpath } async function proceedSee(assertType, text, context, strict = false) { - let description - let allText + let description; + let allText; if (!context) { - const el = await this.context + const el = await this.context; - allText = el.constructor.name !== 'Locator' ? [await el.locator('body').innerText()] : [await el.innerText()] + allText = el.constructor.name !== 'Locator' ? [await el.locator('body').innerText()] : [await el.innerText()]; - description = 'web application' + description = 'web application'; } else { - const locator = new Locator(context, 'css') - description = `element ${locator.toString()}` - const els = await this._locate(locator) - assertElementExists(els, locator.toString()) - allText = await Promise.all(els.map((el) => el.innerText())) + const locator = new Locator(context, 'css'); + description = `element ${locator.toString()}`; + const els = await this._locate(locator); + assertElementExists(els, locator.toString()); + allText = await Promise.all(els.map(el => el.innerText())); } if (strict) { - return allText.map((elText) => equals(description)[assertType](text, elText)) + return allText.map(elText => equals(description)[assertType](text, elText)); } - return stringIncludes(description)[assertType]( - normalizeSpacesInString(text), - normalizeSpacesInString(allText.join(' | ')), - ) + return stringIncludes(description)[assertType](normalizeSpacesInString(text), normalizeSpacesInString(allText.join(' | '))); } async function findCheckable(locator, context) { - let contextEl = await this.context + let contextEl = await this.context; if (typeof context === 'string') { - contextEl = await findElements.call(this, contextEl, new Locator(context, 'css').simplify()) - contextEl = contextEl[0] + contextEl = await findElements.call(this, contextEl, new Locator(context, 'css').simplify()); + contextEl = contextEl[0]; } - const matchedLocator = new Locator(locator) + const matchedLocator = new Locator(locator); if (!matchedLocator.isFuzzy()) { - return findElements.call(this, contextEl, matchedLocator.simplify()) + return findElements.call(this, contextEl, matchedLocator.simplify()); } - const literal = xpathLocator.literal(locator) - let els = await findElements.call(this, contextEl, Locator.checkable.byText(literal)) + const literal = xpathLocator.literal(locator); + let els = await findElements.call(this, contextEl, Locator.checkable.byText(literal)); if (els.length) { - return els + return els; } - els = await findElements.call(this, contextEl, Locator.checkable.byName(literal)) + els = await findElements.call(this, contextEl, Locator.checkable.byName(literal)); if (els.length) { - return els + return els; } - return findElements.call(this, contextEl, locator) + return findElements.call(this, contextEl, locator); } async function proceedIsChecked(assertType, option) { - let els = await findCheckable.call(this, option) - assertElementExists(els, option, 'Checkable') - els = await Promise.all(els.map((el) => el.isChecked())) - const selected = els.reduce((prev, cur) => prev || cur) - return truth(`checkable ${option}`, 'to be checked')[assertType](selected) + let els = await findCheckable.call(this, option); + assertElementExists(els, option, 'Checkable'); + els = await Promise.all(els.map(el => el.isChecked())); + const selected = els.reduce((prev, cur) => prev || cur); + return truth(`checkable ${option}`, 'to be checked')[assertType](selected); } async function findFields(locator) { - const matchedLocator = new Locator(locator) + const matchedLocator = new Locator(locator); if (!matchedLocator.isFuzzy()) { - return this._locate(matchedLocator) + return this._locate(matchedLocator); } - const literal = xpathLocator.literal(locator) + const literal = xpathLocator.literal(locator); - let els = await this._locate({ xpath: Locator.field.labelEquals(literal) }) + let els = await this._locate({ xpath: Locator.field.labelEquals(literal) }); if (els.length) { - return els + return els; } - els = await this._locate({ xpath: Locator.field.labelContains(literal) }) + els = await this._locate({ xpath: Locator.field.labelContains(literal) }); if (els.length) { - return els + return els; } - els = await this._locate({ xpath: Locator.field.byName(literal) }) + els = await this._locate({ xpath: Locator.field.byName(literal) }); if (els.length) { - return els + return els; } - return this._locate({ css: locator }) + return this._locate({ css: locator }); } async function proceedSeeInField(assertType, field, value) { - const els = await findFields.call(this, field) - assertElementExists(els, field, 'Field') - const el = els[0] - const tag = await el.evaluate((e) => e.tagName) - const fieldType = await el.getAttribute('type') + const els = await findFields.call(this, field); + assertElementExists(els, field, 'Field'); + const el = els[0]; + const tag = await el.evaluate(e => e.tagName); + const fieldType = await el.getAttribute('type'); - const proceedMultiple = async (elements) => { - const fields = Array.isArray(elements) ? elements : [elements] + const proceedMultiple = async elements => { + const fields = Array.isArray(elements) ? elements : [elements]; - const elementValues = [] + const elementValues = []; for (const element of fields) { - elementValues.push(await element.inputValue()) + elementValues.push(await element.inputValue()); } if (typeof value === 'boolean') { - equals(`no. of items matching > 0: ${field}`)[assertType](value, !!elementValues.length) + equals(`no. of items matching > 0: ${field}`)[assertType](value, !!elementValues.length); } else { if (assertType === 'assert') { - equals(`select option by ${field}`)[assertType](true, elementValues.length > 0) + equals(`select option by ${field}`)[assertType](true, elementValues.length > 0); } - elementValues.forEach((val) => stringIncludes(`fields by ${field}`)[assertType](value, val)) + elementValues.forEach(val => stringIncludes(`fields by ${field}`)[assertType](value, val)); } - } + }; if (tag === 'SELECT') { if (await el.getAttribute('multiple')) { - const selectedOptions = await el.all('option:checked') - if (!selectedOptions.length) return null + const selectedOptions = await el.all('option:checked'); + if (!selectedOptions.length) return null; - const options = await filterFieldsByValue(selectedOptions, value, true) - return proceedMultiple(options) + const options = await filterFieldsByValue(selectedOptions, value, true); + return proceedMultiple(options); } - return el.inputValue() + return el.inputValue(); } if (tag === 'INPUT') { if (fieldType === 'checkbox' || fieldType === 'radio') { if (typeof value === 'boolean') { // Filter by values - const options = await filterFieldsBySelectionState(els, true) - return proceedMultiple(options) + const options = await filterFieldsBySelectionState(els, true); + return proceedMultiple(options); } - const options = await filterFieldsByValue(els, value, true) - return proceedMultiple(options) + const options = await filterFieldsByValue(els, value, true); + return proceedMultiple(options); } - return proceedMultiple(els[0]) + return proceedMultiple(els[0]); } - let fieldVal + let fieldVal; try { - fieldVal = await el.inputValue() + fieldVal = await el.inputValue(); } catch (e) { if (e.message.includes('Error: Node is not an ,