UNPKG

playwright-bdd

Version:
234 lines 9.9 kB
"use strict"; /** * Helper to format Playwright test file. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.Formatter = void 0; exports.indent = indent; exports.extractPickleIdFromLine = extractPickleIdFromLine; exports.extractPickleStepIdsFromLine = extractPickleStepIdsFromLine; const jsStringWrap_1 = require("../utils/jsStringWrap"); const paths_1 = require("../utils/paths"); const const_1 = require("../hooks/const"); class Formatter { constructor(config) { this.config = config; this.fixtureOptions = `{ scope: ${this.quoted('test')}, box: true }`; } fileHeader(featureUri, importTestFrom) { // always use "/" for imports, see #91 const importTestFromFile = (0, paths_1.toPosixPath)(importTestFrom?.file || 'playwright-bdd'); let varName = importTestFrom?.varName || 'test'; if (varName !== 'test') varName = `${varName} as test`; return [ `// Generated from: ${featureUri}`, // prettier-ignore // this.quoted() is not possible for 'import from' as backticks not parsed `import { ${varName} } from ${JSON.stringify(importTestFromFile)};`, '', ]; } describe(title, specialTags, children) { const titleStr = this.quoted(title); const fn = this.withSubFunction('describe', specialTags); const firstLine = `test.${fn}(${titleStr}, () => {`; if (!children.length) return [`${firstLine}});`, '']; return [ firstLine, // prettier-ignore ...this.describeConfigure(specialTags).map(indent), ...this.markAsFailing(specialTags).map(indent), // we don't render test.slow() here, b/c each call of test.slow() multiplies timeout // that is not now tags are assumed to work '', ...children.map(indent), `});`, '', ]; } // todo: rename to background, as it's specific for bg (if (testInfo.error) return;) beforeEach(title, fixtures, children) { const titleStr = title ? `${this.quoted(title)}, ` : ''; const fixturesStr = sortFixtureNames([...fixtures]).join(', '); return [ `test.beforeEach(${titleStr}async ({ ${fixturesStr} }, testInfo) => { if (testInfo.error) return;`, ...children.map(indent), `});`, '', ]; } scenarioHooksCall(type, fixturesNames) { const method = type === 'before' ? 'test.beforeEach' : 'test.afterEach'; const title = type === 'before' ? const_1.BEFORE_EACH_HOOKS_GROUP_NAME : const_1.AFTER_EACH_HOOKS_GROUP_NAME; const runScenarioHooks = '$runScenarioHooks'; const sortedFixturesNames = sortFixtureNames([...fixturesNames]); const fixturesStr = sortedFixturesNames.join(', '); const allFixturesStr = [runScenarioHooks, ...sortedFixturesNames].join(', '); const fn = `({ ${allFixturesStr} }) => ${runScenarioHooks}(${this.quoted(type)}, { ${fixturesStr} })`; return [`${method}(${this.quoted(title)}, ${fn});`]; } workerHooksCall(type, fixturesNames, bddDataVar) { // For beforeAll we run hooks, but for afterAll just register, and run on worker teardown. const method = type === 'beforeAll' ? 'test.beforeAll' : 'test.afterAll'; const runWorkerHooks = type === 'beforeAll' ? '$runBeforeAllHooks' : '$registerAfterAllHooks'; const sortedFixturesNames = sortFixtureNames([...fixturesNames]); const fixturesStr = sortedFixturesNames.join(', '); const allFixturesStr = [runWorkerHooks, ...sortedFixturesNames].join(', '); const title = type === 'beforeAll' ? const_1.BEFORE_ALL_HOOKS_GROUP_NAME : const_1.AFTER_ALL_HOOKS_GROUP_NAME; const fn = `({ ${allFixturesStr} }) => ${runWorkerHooks}(test, { ${fixturesStr} }, ${bddDataVar})`; return [`${method}(${this.quoted(title)}, ${fn});`]; } // eslint-disable-next-line max-params test(title, tags, specialTags, fixtures, pickleId, children) { const titleStr = this.quoted(title); const tagsStr = this.testTags(tags); const fixturesStr = sortFixtureNames([...fixtures]).join(', '); const fn = this.withSubFunction('test', specialTags); const firstLine = `${fn}(${titleStr}, ${tagsStr}async ({ ${fixturesStr} }) => { // test: ${pickleId}`; const lines = [ firstLine, // prettier-ignore // We use test.fail() in the test body instead of test.fail('...', () => { ... }) // It allows to apply .only() / .skip() on failing tests. // See: https://github.com/microsoft/playwright/issues/30662 ...this.markAsFailing(specialTags).map(indent), ...children.map(indent), `});`, '', ]; // Wrap test into anonymous describe in case of retries // See: https://github.com/microsoft/playwright/issues/10825 return specialTags.retries !== undefined ? this.wrapInAnonymousDescribe([ ...this.describeConfigure(specialTags).map(indent), '', ...lines.map(indent), ]) : lines; } // eslint-disable-next-line max-params step(keywordEng, stepText, argument, fixtureNames, pickleStepIds) { const textArg = this.quoted(stepText); const fixtures = fixtureNames.size ? `{ ${sortFixtureNames([...fixtureNames]).join(', ')} }` : ''; const argumentArg = argument ? JSON.stringify(argument) : fixtures ? 'null' : ''; const args = [textArg, argumentArg, fixtures].filter((arg) => arg !== '').join(', '); return `await ${keywordEng}(${args}); // step: ${pickleStepIds.join(',')}`; } /** * Renders test.use() call with fixtures. * * NOTE: don't generate worker-scoped fixtures in test file, * because it forces new worker creation. * See: https://github.com/microsoft/playwright/issues/33316 */ testUse(lines) { return ['test.use({', ...lines.map(indent), '});']; } worldFixture(worldFixtureName) { return [ `$world: [({ ${worldFixtureName} }, use) => use(${worldFixtureName}), ${this.fixtureOptions}],`, ]; } testFixture() { return [`$test: [({}, use) => use(test), ${this.fixtureOptions}],`]; } uriFixture(featureUri) { return [`$uri: [({}, use) => use(${this.quoted(featureUri)}), ${this.fixtureOptions}],`]; } pageFixtureWithPromptAttachment() { return [ `page: async ({ page, $prompt }, use) => {`, ` $prompt.setPage(page);`, ` await use(page);`, `},`, ]; } withSubFunction(baseFn, specialTags) { if (specialTags.only) return `${baseFn}.only`; if (specialTags.skip) return `${baseFn}.skip`; if (specialTags.fixme) return `${baseFn}.fixme`; return baseFn; } describeConfigure({ retries, timeout, mode }) { const options = {}; if (retries !== undefined) options.retries = retries; if (timeout !== undefined) options.timeout = timeout; if (mode !== undefined) options.mode = mode; return Object.keys(options).length ? [`test.describe.configure(${JSON.stringify(options)});`] : []; } wrapInAnonymousDescribe(lines) { return [ 'test.describe(() => {', // prettier-ignore ...lines, `});`, '', ]; } markAsFailing(specialTags) { return specialTags.fail ? [`test.fail();`] : []; } testTags(tags) { return tags.length ? `{ tag: [${tags.map((tag) => this.quoted(tag)).join(', ')}] }, ` : ''; } /** * Apply this function only to string literals (mostly titles here). * Objects and arrays are handled with JSON.stringify, * b/c object keys can't be in backticks. * See: https://stackoverflow.com/questions/33194138/template-string-as-object-property-name */ quoted(str) { return (0, jsStringWrap_1.jsStringWrap)(str, { quotes: this.config.quotes }); } } exports.Formatter = Formatter; function indent(value) { return value ? `${' '}${value}` : value; } function extractPickleIdFromLine(line) { const match = line.match(/\/\/ test: ([0-9a-f-]+)$/); if (match) { return { pickleId: match[1], index: match.index }; } } /** * Extracts pickle step IDs from the comment at the end of the line. * Note: * - we can't use line.endsWith(`// step: ${gherkinStep.id}`), because for Scenario Outline * the same step is rendered multiple times with different pickle step ids. * - we can't use line.endsWith(`// step: ${pickleStep.id}`), because for Background * the same step is referenced by multiple pickle steps. */ function extractPickleStepIdsFromLine(line) { const match = line.match(/\/\/ step: ([0-9a-f-,]+)$/); const matchedString = match?.[1]; if (matchedString) { return { pickleStepIds: matchedString.split(','), index: match.index }; } } /** * Show (Given, When, Then, And, But) first, then user fixtures in alphabetical order. */ function sortFixtureNames(fixtureNames) { const stepFixtureNamesOrder = ['Given', 'When', 'Then', 'And', 'But']; return fixtureNames.sort((a, b) => { const indexA = stepFixtureNamesOrder.indexOf(a); const indexB = stepFixtureNamesOrder.indexOf(b); if (indexA !== -1 && indexB !== -1) return indexA - indexB; if (indexA !== -1) return -1; if (indexB !== -1) return 1; return a.localeCompare(b); }); } //# sourceMappingURL=formatter.js.map