diff --git a/bin/test.js b/bin/test.js new file mode 100755 index 00000000..21e6cf3e --- /dev/null +++ b/bin/test.js @@ -0,0 +1,160 @@ +#!/usr/bin/env node +import { ConfigLoaderError, readConfig } from '@web/config-loader'; +import commandLineArgs from 'command-line-args'; +import commandLineUsage from 'command-line-usage'; +import process from 'node:process'; +import { startTestRunner } from '@web/test-runner'; +import { WTRConfig } from '../src/server/wtr-config.js'; + +const DISALLOWED_OPTIONS = ['--browsers', '--playwright', '--puppeteer', '--groups', '--manual']; + +const optionDefinitions = [ + // @web/test-runner options + { + name: 'files', + type: String, + multiple: true, + description: 'Test files to run. Path or glob.\n[Default: ./test/**/*..js]', + order: 9 + }, + { + name: 'open', + type: Boolean, + description: 'Open the browser in headed mode' + }, + { + name: 'slowmo', + type: Number, + description: 'Slows down test operations by the specified number of milliseconds. Useful so that you can see what is going on.' + }, + { + name: 'group', + type: String, + required: true, + defaultOption: true, + description: 'Name of the group to run tests for\n[Default: test]', + order: 1 + }, + { + name: 'watch', + type: Boolean, + description: 'Reload tests on file changes. Allows debugging in all browsers.', + order: 10 + }, + + // d2l-test options + { + name: 'chrome', + type: Boolean, + description: 'Run tests in Chromium', + order: 2 + }, + { + name: 'config', + alias: 'c', + type: String, + description: 'Location to read config file from\n[Default: ./d2l-test.config.js]', + order: 11 + }, + { + name: 'filter', + alias: 'f', + type: String, + multiple: true, + description: 'Filter test files by replacing wildcards with this glob', + order: 6 + }, + { + name: 'firefox', + type: Boolean, + description: 'Run tests in Firefox', + order: 3 + }, + { + name: 'golden', + type: Boolean, + description: 'Generate new golden screenshots', + order: 12 + }, + { + name: 'grep', + alias: 'g', + type: String, + description: 'Only run tests matching this string or regexp', + order: 7 + }, + { + name: 'help', + type: Boolean, + description: 'Print usage information and exit', + order: 13 + }, + { + name: 'retries', + type: Number, + description: 'Number of times to retry failed tests\n[Default: 0]', + order: 8 + }, + { + name: 'safari', + type: Boolean, + description: 'Run tests in Webkit', + order: 4 + }, + { + name: 'timeout', + alias: 't', + type: Number, + description: 'Test timeout threshold in ms\n[Default: 2000]', + order: 5 + }, +]; + +const cliArgs = commandLineArgs(optionDefinitions, { partial: true }); + +if (cliArgs.help) { + const help = commandLineUsage([ + { + header: 'D2L Test', + content: 'Test runner for D2L components and applications' + }, + { + header: 'Usage', + content: 'd2l-test [options]', + }, + { + header: 'Options', + optionList: optionDefinitions + .map(o => (o.description += '\n') && o) + .sort((a, b) => (a.order > b.order ? 1 : -1)) + } + ]); + process.stdout.write(help); + process.exit(); +} + +cliArgs._unknown = cliArgs._unknown?.filter(o => !DISALLOWED_OPTIONS.includes(o)); + +const testConfig = await readConfig('d2l-test.config', cliArgs.config).catch(err => { + if (err instanceof ConfigLoaderError) { + throw new Error(err.message); + } else { + throw err; + } +}) || {}; + +const wtrConfig = new WTRConfig(cliArgs); +const config = wtrConfig.create(testConfig); + +const argv = [ + '--group', cliArgs.group, + ...(cliArgs._unknown || []) +]; +// copy cli-only wtr options back to argv to be processed +cliArgs.watch && argv.push('--watch'); + +await startTestRunner({ + argv, + config, + readFileConfig: false +}); diff --git a/package-lock.json b/package-lock.json index 18c6294e..7cac246f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,14 +10,19 @@ "license": "Apache-2.0", "dependencies": { "@open-wc/testing": "^3", + "@web/config-loader": "^0.2", "@web/test-runner": "^0.16", "@web/test-runner-commands": "^0.7", "@web/test-runner-playwright": "^0.10", "command-line-args": "^5", + "command-line-usage": "^7", "glob": "^10", "pixelmatch": "^5", "pngjs": "^7" }, + "bin": { + "d2l-test": "bin/test.js" + }, "devDependencies": { "@rollup/plugin-node-resolve": "^15", "@web/dev-server": "^0.2", diff --git a/package.json b/package.json index 00c3a649..5a0c5900 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,12 @@ "test": "npm run lint && npm run test:server && npm run test:browser", "test:browser": "web-test-runner --files \"./test/browser/**/*.test.js\" --node-resolve --playwright", "test:server": "mocha ./test/server/**/*.test.js", - "test:vdiff": "web-test-runner --config ./test/browser/wtr-vdiff.config.js --group vdiff", + "test:vdiff": "d2l-test --config ./test/browser/d2l-test.config.js --group vdiff", "test:vdiff:golden": "npm run test:vdiff -- --golden" }, + "bin": { + "d2l-test": "./bin/test.js" + }, "author": "D2L Corporation", "license": "Apache-2.0", "devDependencies": { @@ -30,8 +33,7 @@ "sinon": "^15" }, "exports": { - ".": "./src/browser/index.js", - "./wtr-config.js": "./src/server/index.js" + ".": "./src/browser/index.js" }, "files": [ "/src" @@ -41,10 +43,12 @@ }, "dependencies": { "@open-wc/testing": "^3", + "@web/config-loader": "^0.2", "@web/test-runner": "^0.16", "@web/test-runner-commands": "^0.7", "@web/test-runner-playwright": "^0.10", "command-line-args": "^5", + "command-line-usage": "^7", "glob": "^10", "pixelmatch": "^5", "pngjs": "^7" diff --git a/src/browser/commands.js b/src/browser/commands.js index 9ba41a45..900e1ae7 100644 --- a/src/browser/commands.js +++ b/src/browser/commands.js @@ -30,6 +30,7 @@ export async function focusElem(elem) { export async function hoverAt(x, y) { await sendMouse({ type: 'move', position: [x, y] }); + if (window.d2lTest) window.d2lTest.hovering = true; } export async function hoverElem(elem) { diff --git a/src/browser/fixture.js b/src/browser/fixture.js index 4818e34f..b57f55c5 100644 --- a/src/browser/fixture.js +++ b/src/browser/fixture.js @@ -51,5 +51,18 @@ export async function fixture(element, opts = {}) { await Promise.all([reset(opts), document.fonts.ready]); const elem = await wcFixture(element); await waitForElem(elem, opts.awaitLoadingComplete); + + await pause(); return elem; } + +async function pause() { + const test = window.d2lTest || {}; + + test.update?.(); + + if (test.pause) { + await test.pause; + if (test.pause) test.pause = new Promise(r => test.run = r); + } +} diff --git a/src/browser/vdiff.js b/src/browser/vdiff.js index ca704a55..ff4069e0 100644 --- a/src/browser/vdiff.js +++ b/src/browser/vdiff.js @@ -1,20 +1,32 @@ -import { chai, expect } from '@open-wc/testing'; +import { chai, expect, nextFrame } from '@open-wc/testing'; import { executeServerCommand } from '@web/test-runner-commands'; -let test; +// start loading fonts early +[...document.fonts].map(font => font.load()); -chai.Assertion.addMethod('golden', function(...args) { - return ScreenshotAndCompare.call({ test, elem: this._obj }, ...args); // eslint-disable-line no-invalid-this +let test, soonPromise; + +/* eslint-disable no-undef, no-invalid-this */ +chai.Assertion.addMethod('golden', async function(...args) { + await soonPromise?.catch(err => expect.fail(err)); + return ScreenshotAndCompare.call({ test, elem: this._obj }, ...args); }); -mocha.setup({ // eslint-disable-line no-undef + +mocha.setup({ rootHooks: { beforeEach() { test = this.currentTest; } } }); +/* eslint-enable */ async function ScreenshotAndCompare(opts) { + if (window.d2lTest) { + inlineStyles(this.elem); + document.documentElement.classList.add('screenshot'); + } + const name = this.test.fullTitle(); const rect = this.elem.getBoundingClientRect(); let result = await executeServerCommand('brightspace-visual-diff-compare', { name, rect, opts }); @@ -22,7 +34,52 @@ async function ScreenshotAndCompare(opts) { this.test.timeout(0); result = await executeServerCommand('brightspace-visual-diff-compare-resize', { name }); } + + if (window.d2lTest) document.documentElement.classList.remove('screenshot'); + if (!result.pass) { + if (window.d2lTest?.pause) { + window.d2lTest.fail(); + await window.d2lTest.retryResponse; + } expect.fail(result.message); } + + window.d2lTest?.pass(); +} + +const disallowedProps = ['width', 'inline-size']; +let count = 0; +function inlineStyles(elem) { + // headed chrome takes screenshots by first moving and locking down the element, + // which breaks the hover state. So, copy current styles inline before screenshot + if (!window.d2lTest.hovering || !window.chrome) return; + + count += 1; + + elem.classList.add(`__d2lTestHovering-${count}`); + + [...elem.children, ...elem.shadowRoot?.children ?? []].forEach(child => inlineStyles(child)); + + const computedStyle = getComputedStyle(elem); + [...computedStyle].forEach(prop => { + if (!disallowedProps.includes(prop)) { + elem.style[prop] = computedStyle.getPropertyValue(prop); + } + }); + + ['before', 'after'].forEach(pseudoEl => { + const computedStyle = getComputedStyle(elem, `::${pseudoEl}`); + if (computedStyle.content !== 'none') { + const sheet = new CSSStyleSheet(); + + const props = [...computedStyle].map(prop => { + const value = computedStyle.getPropertyValue(prop); + return disallowedProps.includes(prop) ? '' : `${prop}: ${value} !important;`; + }).join(''); + + sheet.insertRule(`.__d2lTestHovering-${count}::${pseudoEl} {${props}}`); + elem.getRootNode().adoptedStyleSheets.push(sheet); + } + }); } diff --git a/src/server/headed-mode-plugin.js b/src/server/headed-mode-plugin.js index cc4c757d..95877683 100644 --- a/src/server/headed-mode-plugin.js +++ b/src/server/headed-mode-plugin.js @@ -1,15 +1,17 @@ +import { dirname, join } from 'node:path'; import { globSync } from 'glob'; -export function headedMode({ manual, watch, pattern }) { +export function headedMode({ open, watch, pattern }) { const files = globSync(pattern, { ignore: 'node_modules/**', posix: true }); return { name: 'brightspace-headed-mode', async transform(context) { - if ((watch || manual) && files.includes(context.path.slice(1))) { - watch && await new Promise(r => setTimeout(r, 2000)); - return `debugger;\n${context.body}`; + + if ((watch || open) && files.includes(context.path.slice(1))) { + const pausePath = join(dirname(import.meta.url), 'pause.js').replace('file:', ''); + return `debugger;\nimport '${pausePath}'\n${context.body}`; } } }; diff --git a/src/server/index.js b/src/server/index.js deleted file mode 100644 index 0c69be54..00000000 --- a/src/server/index.js +++ /dev/null @@ -1 +0,0 @@ -export { createConfig, getBrowsers } from './wtr-config.js'; diff --git a/src/server/pause.js b/src/server/pause.js new file mode 100644 index 00000000..43b3ebe6 --- /dev/null +++ b/src/server/pause.js @@ -0,0 +1,315 @@ +import * as commands from '../browser/commands.js'; + +const test = window.d2lTest = { commands }; + +test.pause = new Promise(r => test.start = r); + +const controls = ` + +
+
+ .${window.__WTR_CONFIG__.testFile.split('?')[0]} + + +
+ +
+`; + +document.body.insertAdjacentHTML('afterBegin', controls); + +document.querySelector('#skip-all-button').addEventListener('click', skipAll); + +const skipBtn = document.querySelector('#skip-button'); +skipBtn.addEventListener('click', skip); + +const startBtn = document.querySelector('#start-button'); +startBtn.addEventListener('click', start); + +const runAllBtn = document.querySelector('#run-all-button'); +runAllBtn.addEventListener('click', runAll); + +const runBtn = document.querySelector('#run-button'); +runBtn.addEventListener('click', run); + +const testName = document.querySelector('#test-name'); +const rootName = document.querySelector('#root-name'); + +const retryBtn = document.querySelector('#retry-button'); +retryBtn.addEventListener('click', retry); +retryBtn.remove(); + +/* eslint-disable no-undef, no-invalid-this */ +let currentTest, result, retryResponded, focusEl = runBtn; +beforeEach(async function() { + + test.hovering = false; + + const fixture = new Promise(r => test.update = r); + + result = new Promise((pass, fail) => { + test.pass = pass; + test.fail = () => { + test.retryResponse = new Promise(r => retryResponded = r); + fail(); + }; + }); + + currentTest = this.currentTest; + + if (test.skipAll) this.test.parent.ctx.skip(); + + setTimeout(async() => { + await fixture; + + const titlePath = currentTest.titlePath(); + testName.innerHTML = testName.title = titlePath.slice(1).join(' > '); + rootName.innerText = titlePath[0]; + + if (test.pause) { + runBtn.disabled = false; + focusEl.focus(); + await result.catch(showRetry); + } + }); +}); +/* eslint-enable */ + +function start() { + document.querySelector('#start').hidden = true; + document.querySelector('#run').hidden = false; + test.start(); +} + +function run() { + focusEl = runBtn; + runBtn.disabled = true; + test.run(); +} + +function runAll() { + runAllBtn.disabled = true; + test.pause = null; + run(); +} + +function skip() { + run(); + focusEl = skipBtn; + try { + currentTest.skip(); + } catch (e) { null; } +} + +function skipAll() { + test.skipAll = true; + test.start(); +} + +let retryTimeout; +function showRetry() { + if ('retries' in window.__WTR_CONFIG__.testFrameworkConfig) return retryResponded(); + + document.querySelector('#retry-cell').insertAdjacentElement('afterBegin', retryBtn); + retryBtn.focus(); + + retryTimeout = setTimeout(() => { + retryResponded(); + retryBtn.remove(); + }, 4000); +} + +function retry() { + clearTimeout(retryTimeout); + + retryBtn.remove(); + currentTest._retries = Math.max(1, currentTest._retries + 1); + retryResponded(); +} + +await test.pause; +test.pause = new Promise(r => test.run = r); diff --git a/src/server/visual-diff-reporter.js b/src/server/visual-diff-reporter.js index 10f81bc3..9f397199 100644 --- a/src/server/visual-diff-reporter.js +++ b/src/server/visual-diff-reporter.js @@ -1,8 +1,8 @@ -import { cpSync, mkdirSync, rmSync, writeFileSync } from 'fs'; -import { dirname, join } from 'path'; +import { cpSync, mkdirSync, rmSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; import { getTestInfo, PATHS } from './visual-diff-plugin.js'; -import { execSync } from 'child_process'; -import { fileURLToPath } from 'url'; +import { execSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -112,7 +112,7 @@ export function visualDiffReporter({ reportResults = true } = {}) { cpSync(inputDir, tempDir, { force: true, recursive: true }); writeFileSync(join(tempDir, 'data.js'), `export default ${json};`); - execSync(`rollup -c ${join(__dirname, './rollup.config.js')}`); + execSync(`npx rollup -c ${join(__dirname, './rollup.config.js')}`, { stdio: 'pipe' }); rmSync(tempDir, { recursive: true }); diff --git a/src/server/wtr-config.js b/src/server/wtr-config.js index 05d68208..a070175e 100644 --- a/src/server/wtr-config.js +++ b/src/server/wtr-config.js @@ -1,32 +1,12 @@ -import commandLineArgs from 'command-line-args'; import { defaultReporter } from '@web/test-runner'; import { headedMode } from './headed-mode-plugin.js'; import { playwrightLauncher } from '@web/test-runner-playwright'; import { visualDiff } from './visual-diff-plugin.js'; import { visualDiffReporter } from './visual-diff-reporter.js'; -const optionDefinitions = [ - // @web/test-runner options - { name: 'files', type: String, multiple: true }, - { name: 'group', type: String }, - { name: 'manual', type: Boolean }, - { name: 'playwright', type: Boolean }, - { name: 'watch', type: Boolean }, - // custom options - { name: 'chromium', type: Boolean }, - { name: 'filter', alias: 'f', type: String, multiple: true }, - { name: 'firefox', type: Boolean }, - { name: 'golden', type: Boolean }, - { name: 'grep', alias: 'g', type: String }, - { name: 'timeout', type: Number }, - { name: 'webkit', type: Boolean }, -]; - -const cliArgs = commandLineArgs(optionDefinitions, { partial: true }); - const DEFAULT_PATTERN = type => `./test/**/*.${type}.js`; -const DEFAULT_VDIFF = false; -const ALLOWED_BROWSERS = ['chromium', 'firefox', 'webkit']; +const ALLOWED_BROWSERS = ['chrome', 'firefox', 'safari']; +const BROWSER_MAP = { chrome: 'chromium', firefox: 'firefox', safari: 'webkit' }; export class WTRConfig { @@ -35,13 +15,14 @@ export class WTRConfig { constructor(cliArgs) { this.#cliArgs = cliArgs || {}; + this.#cliArgs.group ??= 'test'; const requestedBrowsers = ALLOWED_BROWSERS.filter(b => cliArgs?.[b]); this.#requestedBrowsers = requestedBrowsers.length && requestedBrowsers; } get #defaultConfig() { return { - files: this.#getPattern('test'), + groups: [], nodeResolve: true, testRunnerHtml: testFramework => ` @@ -60,11 +41,21 @@ export class WTRConfig { }; } + get #pattern() { + const files = this.#cliArgs.files || [ this.pattern(this.#cliArgs.group) ]; + + if (this.#cliArgs.filter) { + return this.#filterFiles(files); + } + + return files; + } + get visualDiffGroup() { return { name: 'vdiff', - files: this.#getPattern('vdiff'), - browsers: this.getBrowsers(['chromium']), + files: this.#pattern, + browsers: ['chrome'], testRunnerHtml: testFramework => ` @@ -106,12 +97,28 @@ export class WTRConfig { }; } + #filterFiles(files) { + return this.#cliArgs.filter.map(filterStr => { + // replace everything after the last forward slash + return files.map(f => f.replace(/[^/]*$/, fileGlob => { + // create a new glob for each wildcard + const fileGlobs = Array.from(fileGlob.matchAll(/(? { + const arr = fileGlob.split(''); + arr.splice(index, 1, filterStr); + return arr.join(''); + }); + return `+(${fileGlobs.join('|') || fileGlob})`; + })); + }).flat(); + } + #getMochaConfig(timeoutConfig) { const { timeout = timeoutConfig, + retries, grep, watch, - manual + open } = this.#cliArgs; if (typeof timeout !== 'undefined' && typeof timeout !== 'number') throw new TypeError('timeout must be a number'); @@ -119,51 +126,19 @@ export class WTRConfig { const config = {}; if (timeout) config.timeout = String(timeout); - if (watch || manual) config.timeout = '0'; + if (retries > -1) config.retries = Number(retries); + if (watch || open) config.timeout = '0'; if (grep) config.grep = grep; return Object.keys(config).length && { testFramework: { config } }; } - #getPattern(type) { - const pattern = this.#cliArgs.files || this.pattern(type); - - // replace filename wildcards with all filter strings - // e.g. If filter is ['button', 'list'], pattern './test/*.test.*' becomes: - // [ './test/+(*button*.test.*|*.test.*button*)', './test/+(*list*.test.*|*.test.*list*)' ] - if (this.#cliArgs.filter) { - return this.#cliArgs.filter.map(filterStr => { - // replace everything after the last forward slash - return pattern.replace(/[^/]*$/, fileGlob => { - // create a new glob for each wildcard - const fileGlobs = Array.from(fileGlob.matchAll(/(? { - const arr = fileGlob.split(''); - arr.splice(index, 1, `*${filterStr}*`); - return arr.join(''); - }); - return `+(${fileGlobs.join('|')})`; - }); - }); - } - return pattern; - } - create({ pattern = DEFAULT_PATTERN, - vdiff = DEFAULT_VDIFF, timeout, ...passthroughConfig } = {}) { - - const { files, filter, golden, grep, group, manual, playwright, watch } = this.#cliArgs; - - if (!group || group === 'default') { - if (playwright) { - console.warn('Warning: reducedMotion disabled. Use the unit group to enable reducedMotion.'); - } else { - console.warn('Warning: Running with puppeteer, reducedMotion disabled. Use the unit group to use playwright with reducedMotion enabled'); - } - } + const { files, filter, golden, grep, group, open, watch } = this.#cliArgs; delete passthroughConfig.browsers; @@ -176,14 +151,18 @@ export class WTRConfig { ...passthroughConfig }; - config.groups ??= []; - config.groups.push({ - name: 'unit', - files: this.#getPattern('test'), - browsers: this.getBrowsers() - }); + if (filter) { + config.groups.forEach(group => { + group.files = this.#filterFiles([ group.files ].flat()); + }); + } - if (vdiff) { + if (group === 'test') { + config.groups.push({ + name: 'test', + files: this.#pattern + }); + } else if (group === 'vdiff') { config.reporters ??= [ defaultReporter() ]; config.reporters.push(visualDiffReporter({ reportResults: !golden })); @@ -193,13 +172,17 @@ export class WTRConfig { config.groups.push(this.visualDiffGroup); } - if (watch || manual) { + // convert all browsers to playwright + config.groups.forEach(g => g.browsers = this.getBrowsers(g.browsers)); + + if (watch || open) { + config.testsFinishTimeout = 600000; config.plugins ??= []; - const currentPattern = files || config.groups.find(g => g.name === group)?.files || config.files; + const currentPattern = files || config.groups.find(g => g.name === group)?.files; config.plugins.push(headedMode({ pattern: currentPattern, - manual, + open, watch })); } @@ -213,20 +196,15 @@ export class WTRConfig { if (!Array.isArray(browsers)) throw new TypeError('browsers must be an array'); return browsers.map((b) => playwrightLauncher({ - concurrency: b === 'firefox' ? 1 : undefined, // focus in Firefox unreliable if concurrency > 1 (https://github.com/modernweb-dev/web/issues/238) - product: b, - createBrowserContext: ({ browser }) => browser.newContext({ deviceScaleFactor: 2, reducedMotion: 'reduce' }) + concurrency: b === 'firefox' || this.#cliArgs.open ? 1 : undefined, // focus in Firefox unreliable if concurrency > 1 (https://github.com/modernweb-dev/web/issues/238) + product: BROWSER_MAP[b], + createBrowserContext: ({ browser }) => browser.newContext({ deviceScaleFactor: 2, reducedMotion: 'reduce' }), + launchOptions: { + headless: !this.#cliArgs.open, + devtools: false, + slowMo: this.#cliArgs.slowmo || 0 + } })); } } - -export function createConfig(...args) { - const wtrConfig = new WTRConfig(cliArgs); - return wtrConfig.create(...args); -} - -export function getBrowsers(browsers) { - const wtrConfig = new WTRConfig(cliArgs); - return wtrConfig.getBrowsers(browsers); -} diff --git a/test/browser/wtr-vdiff.config.js b/test/browser/d2l-test.config.js similarity index 60% rename from test/browser/wtr-vdiff.config.js rename to test/browser/d2l-test.config.js index 1ee0a3d7..62d4bd85 100644 --- a/test/browser/wtr-vdiff.config.js +++ b/test/browser/d2l-test.config.js @@ -1,7 +1,4 @@ import { argv } from 'node:process'; -import { createConfig } from '../../src/server/wtr-config.js'; - -const pattern = type => `test/browser/**/*.${type}.js`; function getGoldenFlag() { return { @@ -13,8 +10,7 @@ function getGoldenFlag() { }; } -export default createConfig({ - pattern, - vdiff: true, +export default { + pattern: type => `test/browser/**/*.${type}.js`, plugins: [getGoldenFlag()] -}); +}; diff --git a/test/server/wtr-config.test.js b/test/server/wtr-config.test.js index 3f573d9b..8e6977b1 100644 --- a/test/server/wtr-config.test.js +++ b/test/server/wtr-config.test.js @@ -169,14 +169,14 @@ describe('createWtrConfig', () => { }); it('should use browsers passed as arguments', () => { - const browsers = wtrConfig.getBrowsers(['webkit']); + const browsers = wtrConfig.getBrowsers(['safari']); expect(browsers).to.have.length(1); expect(browsers[0].name).to.equal('Webkit'); }); it('should only use CLI browsers when provided', () => { - const wtrConfig = new WTRConfig({ firefox: true, webkit: true }); - const browsers = wtrConfig.getBrowsers(['chromium']); + const wtrConfig = new WTRConfig({ firefox: true, safari: true }); + const browsers = wtrConfig.getBrowsers(['chrome']); expect(browsers).to.have.length(2); expect(browsers.map(b => b.name)).to.have.members(['Webkit', 'Firefox']); });