diff --git a/.circleci/config.yml b/.circleci/config.yml index 2e09ca426..b1f545cd7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,14 +5,14 @@ orbs: defaults: &defaults machine: - image: ubuntu-2204:2022.10.1 + image: ubuntu-2204:2023.07.2 docker_layer_caching: false steps: - checkout - run: .circleci/build.sh - browser-tools/install-chrome: + chrome-version: 116.0.5845.96 replace-existing: true - - browser-tools/install-chromedriver - run: command: docker-compose run --rm test-acceptance.puppeteer working_directory: test diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 9b3119d16..511f459da 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -12,7 +12,7 @@ jobs: push_to_registry: name: Build and push Docker image to Docker Hub runs-on: ubuntu-latest - + steps: - name: Check out the repo with latest code uses: actions/checkout@v4 diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 96d8fa5c3..c309f8978 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -37,7 +37,7 @@ jobs: env: PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true - name: Install deps - run: npm install playwright@1.32.3 & npx playwright install-deps + run: npm install playwright@1.35.1 & npx playwright install-deps - name: start a server run: "php -S 127.0.0.1:8000 -t test/data/app &" - name: run chromium tests diff --git a/CHANGELOG.md b/CHANGELOG.md index 73d9abb5b..737c21498 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,66 @@ +## 3.5.6 + +Thanks all to those who contributed to make this release! + +🐛 *Bug Fixes* +* fix: switchTo/within block doesn't switch to expected iframe (#3892) - by @KobeNguyenT +* fix: highlight element doesn't work as expected (#3896) - by @KobeNguyenT +``` + verbose/ highlight TRUE TRUE -> highlight element + verbose/ highlight TRUE FALSE -> no highlight element + verbose/ highlight FALSE TRUE -> no highlight element + verbose/ highlight FALSE FALSE -> no highlight element + ``` +* fix: masked value issue in data table (#3885) - by @KobeNguyenT +``` +const accounts = new DataTable(['role', 'username', 'password']); +accounts.add([ + 'ROLE_A', + process.env['FIRST_USERNAME'], + secret(process.env['FIRST_PASSWORD']), +]); +accounts.add([ + 'ROLE_B', + process.env['SECOND_USERNAME'], + secret(process.env['SECOND_PASSWORD']), +]); + +Data(accounts) + .Scenario( + 'ScenarioTitle', + ({ I, pageObject, current }) => { + I.say("Given I'am logged in"); + I.amOnPage('/'); + loginPage.**sendForm**(current.username, current.password); + ) + + + // output + The test feature -- + The scenario | {"username":"Username","password": ***} + 'The real password: theLoggedPasswordInCleartext' + I.fillField('somePasswordLocator', '****') + ✔ OK in 7ms + + The scenario | {"username":"theSecondUsername","password": ***} + 'The real password: theLoggedPasswordInCleartext' + I.fillField('somePasswordLocator', '****') + ✔ OK in 1ms +``` + +* fix: debug info causes error (#3882) - by @KobeNguyenT + +📖 *Documentation* +* fix: get rid of complaining when using session without await and returning nothing. (#3899) - by @KobeNguyenT +* fix(FileSystem): a typo in writeToFile() (#3897) - by @mirao + +🛩️ *Features* +* feat(translation): add more french keywords and fix deprecated waitForClickable (#3906) - by @andonary +``` +- Add some french keywords for translation +- I.waitForClickable has the same "attends" than I.wait. Using "attends" leads to use the deprecated waitForClickable. Fix it by using different words. +``` + ## 3.5.5 🐛 Bug Fixes diff --git a/Dockerfile b/Dockerfile index 6f395ad96..d9f2a6be5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,6 @@ # Download Playwright and its dependencies FROM mcr.microsoft.com/playwright:v1.35.1 +ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true # Installing the pre-required packages and libraries RUN apt-get update && \ @@ -9,14 +10,12 @@ RUN apt-get update && \ # Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others) # Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer # installs, work. -RUN apt-get update \ - && apt-get install -y wget gnupg \ - && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ - && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ - && apt-get update \ - && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \ - --no-install-recommends \ - && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y gnupg wget && \ + wget --quiet --output-document=- https://dl-ssl.google.com/linux/linux_signing_key.pub | gpg --dearmor > /etc/apt/trusted.gpg.d/google-archive.gpg && \ + echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list && \ + apt-get update && \ + apt-get install -y google-chrome-stable --no-install-recommends && \ + rm -rf /var/lib/apt/lists/* # Add pptr user. @@ -36,7 +35,8 @@ RUN ln -s /codecept/bin/codecept.js /usr/local/bin/codeceptjs RUN mkdir /tests WORKDIR /tests # Install puppeteer so it's available in the container. -RUN npm i puppeteer +RUN npm i puppeteer@21.1.1 +RUN google-chrome --version # Allow to pass argument to codecept run via env variable ENV CODECEPT_ARGS="" diff --git a/docs/helpers/FileSystem.md b/docs/helpers/FileSystem.md index c7dfa0df1..0af9843db 100644 --- a/docs/helpers/FileSystem.md +++ b/docs/helpers/FileSystem.md @@ -140,7 +140,7 @@ I.waitForFile('largeFilesName.txt', 10); // wait 10 seconds for file ### writeToFile -Writes test to file +Writes text to file #### Parameters diff --git a/docs/helpers/Playwright.md b/docs/helpers/Playwright.md index fc31a3ab4..b1f7ea5a1 100644 --- a/docs/helpers/Playwright.md +++ b/docs/helpers/Playwright.md @@ -74,7 +74,7 @@ Type: [object][5] - `ignoreLog` **[Array][9]<[string][8]>?** An array with console message types that are not logged to debug log. Default value is `['warning', 'log']`. E.g. you can set `[]` to log all messages. See all possible [values][44]. - `ignoreHTTPSErrors` **[boolean][32]?** Allows access to untrustworthy pages, e.g. to a page with an expired certificate. Default value is `false` - `bypassCSP` **[boolean][32]?** bypass Content Security Policy or CSP -- `highlightElement` **[boolean][32]?** highlight the interacting elements. Default: false +- `highlightElement` **[boolean][32]?** highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose). diff --git a/docs/helpers/Puppeteer.md b/docs/helpers/Puppeteer.md index 067b6fede..0dcc642ae 100644 --- a/docs/helpers/Puppeteer.md +++ b/docs/helpers/Puppeteer.md @@ -56,7 +56,7 @@ Type: [object][4] - `manualStart` **[boolean][20]?** do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`. - `browser` **[string][6]?** can be changed to `firefox` when using [puppeteer-firefox][2]. - `chrome` **[object][4]?** pass additional [Puppeteer run options][25]. -- `highlightElement` **[boolean][20]?** highlight the interacting elements. Default: false +- `highlightElement` **[boolean][20]?** highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose). diff --git a/docs/helpers/WebDriver.md b/docs/helpers/WebDriver.md index 5220bdca9..e36f9fa48 100644 --- a/docs/helpers/WebDriver.md +++ b/docs/helpers/WebDriver.md @@ -45,7 +45,7 @@ Type: [object][16] - `desiredCapabilities` **[object][16]?** Selenium's [desired capabilities][6]. - `manualStart` **[boolean][32]?** do not start browser before a test, start it manually inside a helper with `this.helpers["WebDriver"]._startBrowser()`. - `timeouts` **[object][16]?** [WebDriver timeouts][37] defined as hash. -- `highlightElement` **[boolean][32]?** highlight the interacting elements. Default: false +- `highlightElement` **[boolean][32]?** highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose). diff --git a/lib/codecept.js b/lib/codecept.js index c72175385..fe749fd6e 100644 --- a/lib/codecept.js +++ b/lib/codecept.js @@ -89,6 +89,9 @@ class Codecept { global.When = stepDefinitions.When; global.Then = stepDefinitions.Then; global.DefineParameterType = stepDefinitions.defineParameterType; + + // debug mode + global.debugMode = false; } } diff --git a/lib/command/info.js b/lib/command/info.js index 48e4e7402..933a469a6 100644 --- a/lib/command/info.js +++ b/lib/command/info.js @@ -37,3 +37,27 @@ module.exports = async function (path) { output.print('Please copy environment info when you report issues on GitHub: https://github.com/Codeception/CodeceptJS/issues'); output.print('***************************************'); }; + +module.exports.getMachineInfo = async () => { + const info = { + nodeInfo: await envinfo.helpers.getNodeInfo(), + osInfo: await envinfo.helpers.getOSInfo(), + cpuInfo: await envinfo.helpers.getCPUInfo(), + chromeInfo: await envinfo.helpers.getChromeInfo(), + edgeInfo: await envinfo.helpers.getEdgeInfo(), + firefoxInfo: await envinfo.helpers.getFirefoxInfo(), + safariInfo: await envinfo.helpers.getSafariInfo(), + }; + + output.print('***************************************'); + for (const [key, value] of Object.entries(info)) { + if (Array.isArray(value)) { + output.print(`${key}: ${value[1]}`); + } else { + output.print(`${key}: ${JSON.stringify(value, null, ' ')}`); + } + } + output.print('If you need more detailed info, just run this: npx codeceptjs info'); + output.print('***************************************'); + return info; +}; diff --git a/lib/command/run-workers.js b/lib/command/run-workers.js index be1a3dc85..20fa44f51 100644 --- a/lib/command/run-workers.js +++ b/lib/command/run-workers.js @@ -40,8 +40,9 @@ module.exports = async function (workerCount, selectedRuns, options) { try { if (options.verbose) { - const getInfo = require('./info'); - await getInfo(); + global.debugMode = true; + const { getMachineInfo } = require('./info'); + await getMachineInfo(); } await workers.bootstrapAll(); await workers.run(); diff --git a/lib/command/run.js b/lib/command/run.js index fba9b3a68..f494e6a20 100644 --- a/lib/command/run.js +++ b/lib/command/run.js @@ -30,8 +30,9 @@ module.exports = async function (test, options) { codecept.loadTests(test); if (options.verbose) { - const getInfo = require('./info'); - await getInfo(); + global.debugMode = true; + const { getMachineInfo } = require('./info'); + await getMachineInfo(); } await codecept.run(); diff --git a/lib/data/context.js b/lib/data/context.js index 8b26b76fb..4d6d21bdf 100644 --- a/lib/data/context.js +++ b/lib/data/context.js @@ -23,6 +23,7 @@ module.exports = function (context) { .inject({ current: dataRow.data })); } }); + maskSecretInTitle(scenarios); return new DataScenarioConfig(scenarios); }, only: { @@ -42,6 +43,7 @@ module.exports = function (context) { .inject({ current: dataRow.data })); } }); + maskSecretInTitle(scenarios); return new DataScenarioConfig(scenarios); }, }, @@ -71,12 +73,6 @@ function replaceTitle(title, dataRow) { // it should be printed if (Object.prototype.toString.call(dataRow.data) === (Object()).toString() && dataRow.data.toString() !== (Object()).toString()) { - Object.entries(dataRow.data).forEach(entry => { - const [key, value] = entry; - if (value instanceof Secret) { - dataRow.data[key] = value.getMasked(); - } - }); return `${title} | ${dataRow.data}`; } @@ -119,3 +115,15 @@ function detectDataType(dataTable) { throw new Error('Invalid data type. Data accepts either: DataTable || generator || Array || function'); } + +function maskSecretInTitle(scenarios) { + scenarios.forEach(scenario => { + const res = []; + + scenario.test.title.split(',').forEach(item => { + res.push(item.replace(/{"_secret":"(.*)"}/, '"*****"')); + }); + + scenario.test.title = res.join(','); + }); +} diff --git a/lib/helper/FileSystem.js b/lib/helper/FileSystem.js index a7d3efb62..830936c7d 100644 --- a/lib/helper/FileSystem.js +++ b/lib/helper/FileSystem.js @@ -52,7 +52,7 @@ class FileSystem extends Helper { } /** - * Writes test to file + * Writes text to file * @param {string} name * @param {string} text */ diff --git a/lib/helper/Playwright.js b/lib/helper/Playwright.js index a1b7837e9..2b84f1214 100644 --- a/lib/helper/Playwright.js +++ b/lib/helper/Playwright.js @@ -47,7 +47,6 @@ const { setRestartStrategy, restartsSession, restartsContext, restartsBrowser, } = require('./extras/PlaywrightRestartOpts'); const { createValueEngine, createDisabledEngine } = require('./extras/PlaywrightPropEngine'); -const { highlightElement } = require('./scripts/highlightElement'); const pathSeparator = path.sep; @@ -94,7 +93,7 @@ const pathSeparator = path.sep; * @prop {string[]} [ignoreLog] - An array with console message types that are not logged to debug log. Default value is `['warning', 'log']`. E.g. you can set `[]` to log all messages. See all possible [values](https://playwright.dev/docs/api/class-consolemessage#console-message-type). * @prop {boolean} [ignoreHTTPSErrors] - Allows access to untrustworthy pages, e.g. to a page with an expired certificate. Default value is `false` * @prop {boolean} [bypassCSP] - bypass Content Security Policy or CSP - * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false + * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose). */ const config = {}; @@ -827,8 +826,9 @@ class Playwright extends Helper { async _stopBrowser() { this.withinLocator = null; - this._setPage(null); + await this._setPage(null); this.context = null; + this.frame = null; popupStore.clear(); await this.browser.close(); } @@ -867,6 +867,7 @@ class Playwright extends Helper { this.withinLocator = null; this.context = await this.page; this.contextLocator = null; + this.frame = null; } _extractDataFromPerformanceTiming(timing, ...dataNames) { @@ -1156,6 +1157,9 @@ class Playwright extends Helper { */ async _locate(locator) { const context = await this.context || await this._getContext(); + + if (this.frame) return findElements(this.frame, locator); + return findElements(context, locator); } @@ -1579,7 +1583,7 @@ class Playwright extends Helper { await el.clear(); - highlightActiveElement.call(this, el, await this._getContext()); + await highlightActiveElement.call(this, el); await el.type(value.toString(), { delay: this.options.pressKeyDelay }); @@ -1609,7 +1613,7 @@ class Playwright extends Helper { const el = els[0]; - highlightActiveElement.call(this, el, this.page); + await highlightActiveElement.call(this, el); await el.clear(); @@ -1624,7 +1628,7 @@ class Playwright extends Helper { async appendField(field, value) { const els = await findFields.call(this, field); assertElementExists(els, field, 'Field'); - highlightActiveElement.call(this, els[0], await this._getContext()); + await highlightActiveElement.call(this, els[0]); await els[0].press('End'); await els[0].type(value.toString(), { delay: this.options.pressKeyDelay }); return this._waitForAction(); @@ -1670,7 +1674,7 @@ class Playwright extends Helper { assertElementExists(els, select, 'Selectable field'); const el = els[0]; - highlightActiveElement.call(this, el, await this._getContext()); + await highlightActiveElement.call(this, el); if (!Array.isArray(option)) option = [option]; @@ -1882,11 +1886,11 @@ class Playwright extends Helper { * @returns {Promise} */ async executeScript(fn, arg) { - let context = this.page; - if (this.context && this.context.constructor.name === 'Frame') { - context = this.context; // switching to iframe context + if (this.context && this.context.constructor.name === 'FrameLocator') { + // switching to iframe context + return this.context.locator(':root').evaluate(fn, arg); } - return context.evaluate.apply(context, [fn, arg]); + return this.page.evaluate.apply(this.page, [fn, arg]); } /** @@ -2408,7 +2412,7 @@ class Playwright extends Helper { } async _getContext() { - if (this.context && this.context.constructor.name === 'Frame') { + if (this.context && this.context.constructor.name === 'FrameLocator') { return this.context; } return this.page; @@ -2481,6 +2485,14 @@ class Playwright extends Helper { }, [locator.value, text, $XPath.toString()], { timeout: waitTimeout }); } } else { + // we have this as https://github.com/microsoft/playwright/issues/26829 is not yet implemented + if (this.frame) { + const { setTimeout } = require('timers/promises'); + await setTimeout(waitTimeout); + waiter = await this.frame.locator(`:has-text('${text}')`).first().isVisible(); + if (!waiter) throw new Error(`Text "${text}" was not found on page after ${waitTimeout / 1000} sec`); + return; + } waiter = contextObject.waitForFunction(text => document.body && document.body.innerText.indexOf(text) > -1, text, { timeout: waitTimeout }); } return waiter.catch((err) => { @@ -2535,37 +2547,37 @@ class Playwright extends Helper { } if (locator >= 0 && locator < childFrames.length) { - this.context = childFrames[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'); } return; } - let contentFrame; if (!locator) { - this.context = await this.page.frames()[0]; + this.context = this.page; this.contextLocator = null; + this.frame = null; return; } // iframe by selector - const els = await this._locate(locator); - if (!els[0]) { - throw new Error(`Element ${JSON.stringify(locator)} was not found by text|CSS|XPath`); + 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`); } - // get content of the first iframe - locator = new Locator(locator, 'css'); - if ((locator.frame && locator.frame === 'iframe') || locator.value.toLowerCase() === 'iframe') { - contentFrame = await this.page.frames()[1]; - // get content of the iframe using its name - } else if (locator.value.toLowerCase().includes('name=')) { - const frameName = locator.value.split('=')[1].replace(/"/g, '').replaceAll(/]/g, ''); - contentFrame = await this.page.frame(frameName); + if (this.frame) { + this.frame = await this.frame.frameLocator(locator); + } else { + this.frame = await this.page.frameLocator(locator); } + const contentFrame = this.frame; + if (contentFrame) { this.context = contentFrame; this.contextLocator = null; @@ -3260,7 +3272,7 @@ async function findElement(matcher, locator) { if (locator.react) return findReact(matcher, locator); locator = new Locator(locator, 'css'); - return matcher.locator(buildLocatorString(locator)); + return matcher.locator(buildLocatorString(locator)).first(); } async function getVisibleElements(elements) { @@ -3290,7 +3302,7 @@ async function proceedClick(locator, context = null, options = {}) { assertElementExists(els, locator, 'Clickable element'); } - highlightActiveElement.call(this, els[0], await this._getContext()); + await highlightActiveElement.call(this, els[0]); /* using the force true options itself but instead dispatching a click @@ -3340,13 +3352,9 @@ async function proceedSee(assertType, text, context, strict = false) { let allText; if (!context) { - let el = await this.context; - if (el && !el.getProperty) { - // Fallback to body - el = await this.page.$('body'); - } + const el = await this.context; - allText = [await el.innerText()]; + allText = [await el.locator('body').innerText()]; description = 'web application'; } else { const locator = new Locator(context, 'css'); @@ -3519,8 +3527,7 @@ async function elementSelected(element) { function isFrameLocator(locator) { locator = new Locator(locator); if (locator.isFrame()) { - const _locator = new Locator(locator.value); - return _locator.value; + return locator.value; } return false; } @@ -3716,10 +3723,14 @@ async function saveTraceForContext(context, name) { return fileName; } -function highlightActiveElement(element, context) { - if (!this.options.highlightElement && !store.debugMode) return; - - highlightElement(element, context); +async function highlightActiveElement(element) { + if (this.options.highlightElement && global.debugMode) { + await element.evaluate(el => { + const prevStyle = el.style.boxShadow; + el.style.boxShadow = '0px 0px 4px 3px rgba(255, 0, 0, 0.7)'; + setTimeout(() => el.style.boxShadow = prevStyle, 2000); + }); + } } const createAdvancedTestResults = (url, dataToCheck, requests) => { diff --git a/lib/helper/Puppeteer.js b/lib/helper/Puppeteer.js index 8ca9816f5..89ba6df16 100644 --- a/lib/helper/Puppeteer.js +++ b/lib/helper/Puppeteer.js @@ -69,7 +69,7 @@ const consoleLogStore = new Console(); * @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["Puppeteer"]._startBrowser()`. * @prop {string} [browser=chrome] - can be changed to `firefox` when using [puppeteer-firefox](https://codecept.io/helpers/Puppeteer-firefox). * @prop {object} [chrome] - pass additional [Puppeteer run options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions). - * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false + * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose). */ const config = {}; @@ -2727,7 +2727,7 @@ function getNormalizedKey(key) { } function highlightActiveElement(element, context) { - if (!this.options.highlightElement && !store.debugMode) return; - - highlightElement(element, context); + if (this.options.highlightElement && global.debugMode) { + highlightElement(element, context); + } } diff --git a/lib/helper/WebDriver.js b/lib/helper/WebDriver.js index ca6dd95cb..a6f701a56 100644 --- a/lib/helper/WebDriver.js +++ b/lib/helper/WebDriver.js @@ -62,7 +62,7 @@ const webRoot = 'body'; * @prop {object} [desiredCapabilities] Selenium's [desired capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities). * @prop {boolean} [manualStart=false] - do not start browser before a test, start it manually inside a helper with `this.helpers["WebDriver"]._startBrowser()`. * @prop {object} [timeouts] [WebDriver timeouts](http://webdriver.io/docs/timeouts.html) defined as hash. - * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false + * @prop {boolean} [highlightElement] - highlight the interacting elements. Default: false. Note: only activate under verbose mode (--verbose). */ const config = {}; @@ -2918,9 +2918,9 @@ function isModifierKey(key) { } function highlightActiveElement(element) { - if (!this.options.highlightElement && !store.debugMode) return; - - highlightElement(element, this.browser); + if (this.options.highlightElement && global.debugMode) { + highlightElement(element, this.browser); + } } function prepareLocateFn(context) { diff --git a/lib/helper/scripts/highlightElement.js b/lib/helper/scripts/highlightElement.js index 4bd686937..83e25bd9a 100644 --- a/lib/helper/scripts/highlightElement.js +++ b/lib/helper/scripts/highlightElement.js @@ -7,7 +7,7 @@ module.exports.highlightElement = (element, context) => { }; try { - // Playwright, Puppeteer + // Puppeteer context.evaluate(clientSideHighlightFn, element).catch(err => console.error(err)); } catch (e) { // WebDriver diff --git a/lib/pause.js b/lib/pause.js index b979b1d54..6e74810f3 100644 --- a/lib/pause.js +++ b/lib/pause.js @@ -78,7 +78,6 @@ async function parseInput(cmd) { rl.pause(); next = false; recorder.session.start('pause'); - store.debugMode = false; if (cmd === '') next = true; if (!cmd || cmd === 'resume' || cmd === 'exit') { finish(); @@ -98,7 +97,6 @@ async function parseInput(cmd) { return cmd; }; - store.debugMode = true; let isCustomCommand = false; let lastError = null; let isAiCommand = false; diff --git a/lib/plugin/autoLogin.js b/lib/plugin/autoLogin.js index 0763c2918..1f1b8bde8 100644 --- a/lib/plugin/autoLogin.js +++ b/lib/plugin/autoLogin.js @@ -251,20 +251,16 @@ module.exports = function (config) { } else { userSession.login(I); } - store.debugMode = true; const cookies = await userSession.fetch(I); if (config.saveToFile) { debug(`Saved user session into file for ${name}`); fs.writeFileSync(path.join(global.output_dir, `${name}_session.json`), JSON.stringify(cookies)); } store[`${name}_session`] = cookies; - store.debugMode = false; }; if (!cookies) return loginAndSave(); - store.debugMode = true; - recorder.session.start('check login'); if (shouldAwait) { await userSession.restore(I, cookies); @@ -287,7 +283,6 @@ module.exports = function (config) { }); }); recorder.add(() => { - store.debugMode = false; recorder.session.restore('check login'); }); diff --git a/lib/plugin/retryTo.js b/lib/plugin/retryTo.js index f35237fcd..8d08dadfe 100644 --- a/lib/plugin/retryTo.js +++ b/lib/plugin/retryTo.js @@ -83,7 +83,6 @@ module.exports = function (config) { return retryTo; function retryTo(callback, maxTries, pollInterval = undefined) { - const mode = store.debugMode; let tries = 1; if (!pollInterval) pollInterval = config.pollInterval; @@ -113,7 +112,6 @@ module.exports = function (config) { }; recorder.add('retryTo', async () => { - store.debugMode = true; tryBlock(); }); }).then(() => { diff --git a/lib/plugin/tryTo.js b/lib/plugin/tryTo.js index 3b65e945c..784d54fb8 100644 --- a/lib/plugin/tryTo.js +++ b/lib/plugin/tryTo.js @@ -81,10 +81,8 @@ module.exports = function (config) { }; function tryTo(callback) { - const mode = store.debugMode; let result = false; return recorder.add('tryTo', () => { - store.debugMode = true; recorder.session.start('tryTo'); callback(); recorder.add(() => { @@ -100,7 +98,6 @@ function tryTo(callback) { return result; }); return recorder.add('result', () => { - store.debugMode = mode; return result; }, true, false); }, false, false); diff --git a/lib/session.js b/lib/session.js index c55431f7e..a037f0e21 100644 --- a/lib/session.js +++ b/lib/session.js @@ -19,7 +19,7 @@ const savedSessions = {}; * @param {CodeceptJS.LocatorOrString} sessionName * @param {Function | Object} config * @param {Function} [fn] - * @return {Promise<*> | undefined} + * @return {any} */ function session(sessionName, config, fn) { if (typeof config === 'function') { diff --git a/package.json b/package.json index fd4740e26..2c3dadd72 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeceptjs", - "version": "3.5.5", + "version": "3.5.6", "description": "Supercharged End 2 End Testing Framework for NodeJS", "keywords": [ "acceptance", @@ -40,8 +40,8 @@ "lint": "eslint bin/ examples/ lib/ test/ translations/ runok.js", "lint-fix": "eslint bin/ examples/ lib/ test/ translations/ runok.js --fix", "docs": "./runok.js docs", - "test:unit": "mocha test/unit --recursive --timeout 5000", - "test:runner": "mocha test/runner --recursive --timeout 5000", + "test:unit": "mocha test/unit --recursive --timeout 10000", + "test:runner": "mocha test/runner --recursive --timeout 10000", "test": "npm run test:unit && npm run test:runner", "test:appium-quick": "mocha test/helper/Appium_test.js --grep 'quick'", "test:appium-other": "mocha test/helper/Appium_test.js --grep 'second'", @@ -68,7 +68,7 @@ "acorn": "8.10.0", "arrify": "2.0.1", "axios": "1.3.3", - "chai": "4.3.6", + "chai": "4.3.8", "chai-deep-match": "1.2.1", "chalk": "4.1.2", "commander": "11.0.0", @@ -83,7 +83,7 @@ "html-minifier": "4.0.0", "inquirer": "6.5.2", "joi": "17.6.0", - "js-beautify": "1.14.0", + "js-beautify": "1.14.9", "lodash.clonedeep": "4.5.0", "lodash.merge": "4.6.2", "mkdirp": "1.0.4", @@ -102,13 +102,13 @@ "@codeceptjs/detox-helper": "1.0.2", "@codeceptjs/mock-request": "0.3.1", "@faker-js/faker": "7.6.0", - "@pollyjs/adapter-puppeteer": "6.0.5", + "@pollyjs/adapter-puppeteer": "6.0.6", "@pollyjs/core": "5.1.0", "@types/inquirer": "9.0.3", "@types/node": "20.4.4", "@wdio/sauce-service": "8.3.8", "@wdio/selenium-standalone-service": "8.3.2", - "@wdio/utils": "8.3.0", + "@wdio/utils": "8.16.22", "apollo-server-express": "2.25.3", "chai-as-promised": "7.1.1", "chai-subset": "1.6.0", @@ -116,23 +116,23 @@ "documentation": "12.3.0", "dtslint": "4.1.6", "electron": "26.1.0", - "eslint": "8.45.0", + "eslint": "8.50.0", "eslint-config-airbnb-base": "15.0.0", "eslint-plugin-import": "2.25.4", "eslint-plugin-mocha": "6.3.0", - "expect": "29.6.2", + "expect": "29.7.0", "express": "4.17.2", "graphql": "14.6.0", - "husky": "8.0.1", + "husky": "8.0.3", "inquirer-test": "2.0.1", "jsdoc": "3.6.10", "jsdoc-typeof-plugin": "1.0.0", "json-server": "0.10.1", - "playwright": "1.35.1", + "playwright": "1.38.1", "puppeteer": "21.1.1", "qrcode-terminal": "0.12.0", "rosie": "2.1.0", - "runok": "0.9.2", + "runok": "0.9.3", "sinon": "15.2.0", "sinon-chai": "3.7.0", "testcafe": "3.0.1", @@ -153,4 +153,4 @@ "npm": ">=5.6.0" }, "es6": true -} +} \ No newline at end of file diff --git a/runok.js b/runok.js index 78db397cf..8e600bd44 100755 --- a/runok.js +++ b/runok.js @@ -422,9 +422,25 @@ title: ${name} const newVersion = semver.inc(packageFile.version, type); packageFile.version = newVersion; - fs.writeFileSync('./package.json', JSON.stringify(packageFile, null, 2)); + fs.writeFileSync('./package.json', (JSON.stringify(packageFile, null, 2)).replace(/(^[ \t]*\n)/gm, '')); console.log('Version updated', currentVersion, '=>', newVersion); + const file = 'CHANGELOG.md'; + const changelog = fs.readFileSync(file).toString(); + + const _changelog = `## ${newVersion}\n +Thanks all to those who contributed to make this release! + +🐛 *Bug Fixes* + +📖 *Documentation* + +🛩️ *Features* + +${changelog}`; + + fs.writeFileSync(`./${file}`, _changelog); + console.log('Creating and switching to release branch...'); await exec(`git checkout -b release-${newVersion}`); } diff --git a/test/acceptance/within_test.js b/test/acceptance/within_test.js index 9931c984b..726c75253 100644 --- a/test/acceptance/within_test.js +++ b/test/acceptance/within_test.js @@ -47,9 +47,9 @@ Scenario('within on iframe without iframe navigation @WebDriverIO @Puppeteer @Pl Scenario('within on nested iframe without iframe navigation depth 2 @WebDriverIO @Puppeteer @Playwright', ({ I }) => { I.amOnPage('/iframe_nested'); - within({ frame: ['[name=wrapper]', '[name=content]'] }, async () => { - await I.fillField('rus', 'Updated'); - await I.see('Sign in!'); + within({ frame: ['[name=wrapper]', '[name=content]'] }, () => { + I.fillField('rus', 'Updated'); + I.see('Sign in!'); }); I.see('Nested Iframe test'); I.dontSee('Sign in!'); @@ -57,10 +57,10 @@ Scenario('within on nested iframe without iframe navigation depth 2 @WebDriverIO Scenario('within on nested iframe depth 1 @WebDriverIO @Puppeteer @Playwright', ({ I }) => { I.amOnPage('/iframe'); - within({ frame: ['[name=content]'] }, async () => { - await I.fillField('rus', 'Updated'); - await I.click('Sign in!'); - await I.waitForText('Email Address'); + within({ frame: ['[name=content]'] }, () => { + I.fillField('rus', 'Updated'); + I.click('Sign in!'); + I.waitForText('Email Address'); }); I.see('Iframe test'); I.dontSee('Email Address'); @@ -68,10 +68,10 @@ Scenario('within on nested iframe depth 1 @WebDriverIO @Puppeteer @Playwright', Scenario('within on nested iframe depth 2 @WebDriverIO @Puppeteer @Playwright', ({ I }) => { I.amOnPage('/iframe_nested'); - within({ frame: ['[name=wrapper]', '[name=content]'] }, async () => { - await I.fillField('rus', 'Updated'); - await I.click('Sign in!'); - await I.see('Email Address'); + within({ frame: ['[name=wrapper]', '[name=content]'] }, () => { + I.fillField('rus', 'Updated'); + I.click('Sign in!'); + I.see('Email Address'); }); I.see('Nested Iframe test'); I.dontSee('Email Address'); @@ -79,10 +79,10 @@ Scenario('within on nested iframe depth 2 @WebDriverIO @Puppeteer @Playwright', Scenario('within on nested iframe depth 2 and mixed id and xpath selector @WebDriverIO @Puppeteer @Playwright', ({ I }) => { I.amOnPage('/iframe_nested'); - within({ frame: ['#wrapperId', '[name=content]'] }, async () => { - await I.fillField('rus', 'Updated'); - await I.click('Sign in!'); - await I.see('Email Address'); + within({ frame: ['#wrapperId', '[name=content]'] }, () => { + I.fillField('rus', 'Updated'); + I.click('Sign in!'); + I.see('Email Address'); }); I.see('Nested Iframe test'); I.dontSee('Email Address'); @@ -90,10 +90,10 @@ Scenario('within on nested iframe depth 2 and mixed id and xpath selector @WebDr Scenario('within on nested iframe depth 2 and mixed class and xpath selector @WebDriverIO @Puppeteer @Playwright', ({ I }) => { I.amOnPage('/iframe_nested'); - within({ frame: ['.wrapperClass', '[name=content]'] }, async () => { - await I.fillField('rus', 'Updated'); - await I.click('Sign in!'); - await I.see('Email Address'); + within({ frame: ['.wrapperClass', '[name=content]'] }, () => { + I.fillField('rus', 'Updated'); + I.click('Sign in!'); + I.see('Email Address'); }); I.see('Nested Iframe test'); I.dontSee('Email Address'); diff --git a/test/data/app/controllers.php b/test/data/app/controllers.php index 4e2b6e472..2dea9b435 100755 --- a/test/data/app/controllers.php +++ b/test/data/app/controllers.php @@ -138,7 +138,7 @@ function GET() { include __DIR__ . '/view/image.php'; } } - + class cookies { @@ -177,6 +177,13 @@ public function GET() } } +class iframes { + public function GET() + { + include __DIR__.'/view/iframes.php'; + } +} + class iframe_nested { public function GET() { @@ -311,4 +318,4 @@ class basic_auth { function GET() { include __DIR__.'/view/basic_auth.php'; } -} \ No newline at end of file +} diff --git a/test/data/app/index.php b/test/data/app/index.php index d963a6fb3..6ab0b28a6 100755 --- a/test/data/app/index.php +++ b/test/data/app/index.php @@ -39,6 +39,7 @@ '/external_url' => 'external_url', '/spinner' => 'spinner', '/iframe' => 'iframe', + '/iframes' => 'iframes', '/iframe_nested' => 'iframe_nested', '/dynamic' => 'dynamic', '/timeout' => 'timeout', diff --git a/test/data/app/view/iframes.php b/test/data/app/view/iframes.php new file mode 100755 index 000000000..4d097d939 --- /dev/null +++ b/test/data/app/view/iframes.php @@ -0,0 +1,12 @@ + + + + + + + +
+