Skip to content

Commit

Permalink
Fix flushing logs for several projects, #59
Browse files Browse the repository at this point in the history
  • Loading branch information
vitalets committed Oct 13, 2023
1 parent 08c8b72 commit 9b90209
Show file tree
Hide file tree
Showing 21 changed files with 142 additions and 72 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## dev
* i18n: Generate scenario outlines correctly [#60](https://github.com/vitalets/playwright-bdd/issues/60).
* Check for duplicate fixture names [#52](https://github.com/vitalets/playwright-bdd/issues/52)
* Fix flushing logs for several projects [#59](https://github.com/vitalets/playwright-bdd/issues/59)

## 5.3.0
* Add support for Playwright `1.38`.
Expand Down
37 changes: 21 additions & 16 deletions src/cli/commands/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { once } from 'node:events';
import path from 'node:path';
import { Command } from 'commander';
import { TestFilesGenerator } from '../../gen';
import { exitWithMessage } from '../../utils';
import { loadConfig as loadPlaywrightConfig } from '../../playwright/loadConfig';
import { getEnvConfigs } from '../../config/env';
import { BDDConfig, defaults } from '../../config';
import { ConfigOption, configOption } from '../options';
import { exit } from '../../utils/exit';

const GEN_WORKER_PATH = path.resolve(__dirname, '..', 'worker.js');

Expand All @@ -23,31 +23,36 @@ export const testCommand = new Command('test')
.option('--verbose', `Verbose mode (default: ${Boolean(defaults.verbose)})`)
.action(async (opts: TestCommandOptions) => {
await loadPlaywrightConfig(opts.config);
const configs = Object.values(getEnvConfigs());
assertConfigsCount(configs);
const cliOptions = buildCliOptions(opts);
await generateFilesForConfigs(configs, cliOptions);
const configs = readConfigsFromEnv();
mergeCliOptions(configs, opts);

await generateFilesForConfigs(configs);
});

function buildCliOptions(opts: TestCommandOptions) {
const config: Partial<BDDConfig> = {};
if ('tags' in opts) config.tags = opts.tags;
if ('verbose' in opts) config.verbose = Boolean(opts.verbose);
return config;
function readConfigsFromEnv() {
const configs: BDDConfig[] = Object.values(getEnvConfigs());
assertConfigsCount(configs);
return configs;
}

function mergeCliOptions(configs: BDDConfig[], opts: TestCommandOptions) {
configs.forEach((config) => {
if ('tags' in opts) config.tags = opts.tags;
if ('verbose' in opts) config.verbose = Boolean(opts.verbose);
});
}

export function assertConfigsCount(configs: unknown[]) {
if (configs.length === 0) {
exitWithMessage(`No BDD configs found. Did you use defineBddConfig() in playwright.config.ts?`);
exit(`No BDD configs found. Did you use defineBddConfig() in playwright.config.ts?`);
}
}

async function generateFilesForConfigs(configs: BDDConfig[], cliConfig: Partial<BDDConfig>) {
async function generateFilesForConfigs(configs: BDDConfig[]) {
// run first config in main thread and other in workers (to have fresh require cache)
// See: https://github.com/vitalets/playwright-bdd/issues/32
const tasks = configs.map((config, index) => {
const finalConfig = { ...config, ...cliConfig };
return index === 0 ? new TestFilesGenerator(finalConfig).generate() : runInWorker(finalConfig);
return index === 0 ? new TestFilesGenerator(config).generate() : runInWorker(config);
});

return Promise.all(tasks);
Expand All @@ -58,6 +63,6 @@ async function runInWorker(config: BDDConfig) {
workerData: { config },
});

// todo: check if worker exited with error?
await once(worker, 'exit');
const [exitCode] = await once(worker, 'exit');
if (exitCode) exit();
}
6 changes: 3 additions & 3 deletions src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import path from 'node:path';
import { BDDConfig } from '.';
import { exitWithMessage } from '../utils';
import { exit } from '../utils/exit';

type OutputDir = string;
type EnvConfigs = Record<OutputDir, BDDConfig>;
Expand All @@ -18,7 +18,7 @@ export function saveConfigToEnv(config: BDDConfig) {
// Throw error only if different calls of defineBddConfig() use the same outputDir.
// See: https://github.com/vitalets/playwright-bdd/issues/39#issuecomment-1653805368
if (!isSameConfigs(config, existingConfig)) {
exitWithMessage(
exit(
`When using several calls of defineBddConfig()`,
`please manually provide different "outputDir" option.`,
);
Expand All @@ -34,7 +34,7 @@ export function getConfigFromEnv(outputDir: string) {
outputDir = path.resolve(outputDir);
const config = envConfigs[outputDir];
if (!config) {
exitWithMessage(
exit(
`Config not found for outputDir: "${outputDir}".`,
`Available dirs: ${Object.keys(envConfigs).join('\n')}`,
);
Expand Down
4 changes: 2 additions & 2 deletions src/cucumber/loadFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { IRunConfiguration, IRunEnvironment } from '@cucumber/cucumber/api';
import { GherkinDocument, Pickle, ParseError } from '@cucumber/messages';
import { PickleWithDocument } from '@cucumber/cucumber/lib/api/gherkin';
import { loadSources } from './loadSources';
import { exitWithMessage } from '../utils';
import { exit } from '../utils/exit';

export async function loadFeatures(
runConfiguration: IRunConfiguration,
Expand Down Expand Up @@ -33,6 +33,6 @@ function handleParseErrors(parseErrors: ParseError[]) {
return `Parse error in "${parseError.source.uri}" ${parseError.message}`;
})
.join('\n');
exitWithMessage(message);
exit(message);
}
}
4 changes: 2 additions & 2 deletions src/cucumber/loadSteps.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IRunConfiguration, IRunEnvironment, loadSupport } from '@cucumber/cucumber/api';
import { ISupportCodeLibrary } from '@cucumber/cucumber/lib/support_code_library_builder/types';
import { exitWithMessage } from '../utils';
import { installTransform } from '../playwright/transform';
import { exit } from '../utils/exit';

const cache = new Map<string, Promise<ISupportCodeLibrary>>();

Expand Down Expand Up @@ -33,7 +33,7 @@ export function findStepDefinition(
});
if (matchedSteps.length === 0) return;
if (matchedSteps.length > 1)
exitWithMessage(
exit(
[
`Several step definitions found for text: ${stepText} (${file})`,
...matchedSteps.map((s) => `- ${s.pattern}`),
Expand Down
25 changes: 14 additions & 11 deletions src/gen/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import { loadFeatures } from '../cucumber/loadFeatures';
import { hasTsNodeRegister, loadSteps } from '../cucumber/loadSteps';
import { ISupportCodeLibrary } from '@cucumber/cucumber/lib/support_code_library_builder/types';
import { extractCucumberConfig, BDDConfig } from '../config';
import { exitWithMessage } from '../utils';
import { Snippets } from '../snippets';
import { IRunConfiguration } from '@cucumber/cucumber/api';
import { appendDecoratorSteps } from '../stepDefinitions/decorators/steps';
import { requireTransform } from '../playwright/transform';
import { getPlaywrightConfigDir } from '../config/dir';
import { Logger } from '../utils/logger';
import parseTagsExpression from '@cucumber/tag-expressions';
import { exit, withExitHandler } from '../utils/exit';

/* eslint-disable @typescript-eslint/no-non-null-assertion */

Expand All @@ -37,13 +37,15 @@ export class TestFilesGenerator {
}

async generate() {
await this.loadCucumberConfig();
await Promise.all([this.loadFeatures(), this.loadSteps()]);
this.buildFiles();
await this.checkUndefinedSteps();
this.checkImportCustomTest();
await this.clearOutputDir();
await this.saveFiles();
await withExitHandler(async () => {
await this.loadCucumberConfig();
await Promise.all([this.loadFeatures(), this.loadSteps()]);
this.buildFiles();
await this.checkUndefinedSteps();
this.checkImportCustomTest();
await this.clearOutputDir();
await this.saveFiles();
});
}

async extractSteps() {
Expand Down Expand Up @@ -113,7 +115,7 @@ export class TestFilesGenerator {
const absFeaturePath = path.resolve(configDir, relFeaturePath);
const relOutputPath = path.relative(this.config.featuresRoot, absFeaturePath);
if (relOutputPath.startsWith('..')) {
exitWithMessage(
exit(
`All feature files should be located underneath featuresRoot.`,
`Please change featuresRoot or paths in configuration.\n`,
`featureFile: ${absFeaturePath}\n`,
Expand All @@ -128,15 +130,16 @@ export class TestFilesGenerator {
const undefinedSteps = this.files.reduce((sum, file) => sum + file.undefinedSteps.length, 0);
if (undefinedSteps > 0) {
const snippets = new Snippets(this.files, this.runConfiguration, this.supportCodeLibrary);
await snippets.printSnippetsAndExit();
await snippets.print();
exit();
}
}

private checkImportCustomTest() {
if (this.config.importTestFrom) return;
const hasCustomTest = this.files.some((file) => file.hasCustomTest);
if (hasCustomTest) {
exitWithMessage(
exit(
`When using custom "test" function in createBdd() you should`,
`set "importTestFrom" config option that points to file exporting custom test.`,
);
Expand Down
5 changes: 3 additions & 2 deletions src/gen/testFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ import { findStepDefinition } from '../cucumber/loadSteps';
import { extractFixtureNames } from '../stepDefinitions/createBdd';
import { BDDConfig } from '../config';
import { KeywordType, getStepKeywordType } from '@cucumber/cucumber/lib/formatter/helpers/index';
import { exitWithMessage, template } from '../utils';
import { template } from '../utils';
import { TestPoms, buildFixtureTag } from './testPoms';
import parseTagsExpression from '@cucumber/tag-expressions';
import { TestNode } from './testNode';
import { getStepConfig, isDecorator, isPlaywrightStyle } from '../stepDefinitions/stepConfig';
import { PomNode } from '../stepDefinitions/decorators/poms';
import { exit } from '../utils/exit';

type TestFileOptions = {
doc: GherkinDocument;
Expand Down Expand Up @@ -369,7 +370,7 @@ export class TestFile {
? ` or set one of the following tags: ${suggestedTags}`
: '.';

exitWithMessage(
exit(
`Can't guess fixture for decorator step "${pickleStep.text}" in file: ${this.sourceFile}.`,
`Please refactor your Page Object classes${suggestedTagsStr}`,
);
Expand Down
4 changes: 2 additions & 2 deletions src/gen/testPoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
* -> error, b/c A has 2 possible fixtures.
*/
import { PomNode, getPomNodeByFixtureName } from '../stepDefinitions/decorators/poms';
import { exitWithMessage } from '../utils';
import { exit } from '../utils/exit';

const FIXTURE_TAG_PREFIX = '@fixture:';

Expand Down Expand Up @@ -112,7 +112,7 @@ export class TestPoms {
if (!usedPom.byTag) return;
const childFixturesBySteps = childFixtures.filter((f) => !f.byTag);
if (childFixturesBySteps.length) {
exitWithMessage(
exit(
`Scenario "${this.title}" contains ${childFixturesBySteps.length} step(s)`,
`not compatible with required fixture "${pomNode.fixtureName}"`,
);
Expand Down
4 changes: 2 additions & 2 deletions src/playwright/loadConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import path from 'node:path';
import fs from 'node:fs';
import { requirePlaywrightModule } from './utils';
import { requireTransform } from './transform';
import { exitWithMessage } from '../utils';
import { exit } from '../utils/exit';

export async function loadConfig(cliConfigPath?: string) {
const resolvedConfigFile = resolveConfigFile(cliConfigPath);
Expand All @@ -28,6 +28,6 @@ function getConfigFilePath(cliConfigPath?: string) {
function assertConfigFileExists(resolvedConfigFile: string | null, cliConfigPath?: string) {
if (!resolvedConfigFile || !fs.existsSync(resolvedConfigFile)) {
const configFilePath = getConfigFilePath(cliConfigPath);
exitWithMessage(`Can't find Playwright config file in: ${configFilePath}`);
exit(`Can't find Playwright config file in: ${configFilePath}`);
}
}
18 changes: 9 additions & 9 deletions src/snippets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import { IRunConfiguration, ISupportCodeLibrary } from '@cucumber/cucumber/api';
import { loadSnippetBuilder } from '../cucumber/loadSnippetBuilder';
import { TestFile, UndefinedStep } from '../gen/testFile';
import { exitWithMessage } from '../utils';
import StepDefinitionSnippetBuilder from '@cucumber/cucumber/lib/formatter/step_definition_snippet_builder';
import { logger } from '../utils/logger';
import { getStepConfig, isDecorator, isPlaywrightStyle } from '../stepDefinitions/stepConfig';
Expand All @@ -20,12 +19,13 @@ export class Snippets {
private supportCodeLibrary: ISupportCodeLibrary,
) {}

async printSnippetsAndExit() {
async print() {
this.snippetBuilder = await this.createSnippetBuilder();
const snippets = this.getSnippets();
this.printHeader();
this.printSnippets(snippets);
this.printFooter(snippets);
// exit();
}

private async createSnippetBuilder() {
Expand Down Expand Up @@ -124,18 +124,18 @@ export class Snippets {
}

private printFooter(snippets: string[]) {
exitWithMessage(
logger.error(
`Missing step definitions (${snippets.length}).`,
'Use snippets above to create them.',
this.getWarnOnZeroScannedFiles(),
`Use snippets above to create them.`,
);
this.printWarningOnZeroScannedFiles();
}

private getWarnOnZeroScannedFiles() {
private printWarningOnZeroScannedFiles() {
const { requirePaths, importPaths } = this.supportCodeLibrary.originalCoordinates;
const scannedFilesCount = requirePaths.length + importPaths.length;
return scannedFilesCount === 0 && !this.isDecorators()
? `\nNote that 0 step definition files found, check the config.`
: '';
if (scannedFilesCount === 0 && !this.isDecorators()) {
logger.error(`Note that 0 step definition files found, check the config.`);
}
}
}
4 changes: 2 additions & 2 deletions src/stepDefinitions/createBdd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import { GherkinStepKeyword } from '@cucumber/cucumber/lib/models/gherkin_step_k
import { fixtureParameterNames } from '../playwright/fixtureParameterNames';
import { FixturesArg, KeyValue, TestTypeCommon } from '../playwright/types';
import { TestType } from '@playwright/test';
import { exitWithMessage } from '../utils';
import {
BddAutoInjectFixtures,
test as baseTest,
isBddAutoInjectFixture,
} from '../run/bddFixtures';
import { isParentChildTest } from '../playwright/testTypeImpl';
import { defineStep } from './defineStep';
import { exit } from '../utils/exit';

/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types */

Expand Down Expand Up @@ -59,7 +59,7 @@ function isCustomTest<T extends KeyValue = {}, W extends KeyValue = {}>(
) {
const isCustomTest = Boolean(customTest && customTest !== (baseTest as TestTypeCommon));
if (isCustomTest && customTest && !isParentChildTest(baseTest, customTest)) {
exitWithMessage(`createBdd() should use test extended from "playwright-bdd"`);
exit(`createBdd() should use test extended from "playwright-bdd"`);
}
return isCustomTest;
}
4 changes: 2 additions & 2 deletions src/stepDefinitions/decorators/poms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { TestType } from '@playwright/test';
import { BuiltInFixtures } from '../../playwright/types';
import { BddFixtures } from '../../run/bddFixtures';
import { linkStepsWithPomNode } from './steps';
import { exitWithMessage } from '../../utils';
import { exit } from '../../utils/exit';

type PomClass = Function;

Expand Down Expand Up @@ -56,7 +56,7 @@ function ensureUniqueFixtureName({ fixtureName, className }: PomNode) {
if (!fixtureName) return;
const existingPom = getPomNodeByFixtureName(fixtureName);
if (existingPom)
exitWithMessage(
exit(
`Duplicate fixture name "${fixtureName}"`,
`defined for classes: ${existingPom.className}, ${className}`,
);
Expand Down
6 changes: 3 additions & 3 deletions src/stepDefinitions/defineStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {
defineStep as CucumberDefineStep,
} from '@cucumber/cucumber';
import { GherkinStepKeyword } from '@cucumber/cucumber/lib/models/gherkin_step_keyword';
import { exitWithMessage } from '../utils';
import { CucumberStepFunction, StepConfig } from './stepConfig';
import StepDefinition from '@cucumber/cucumber/lib/models/step_definition';
import { exit } from '../utils/exit';

/**
* Defines step by config.
Expand All @@ -24,8 +24,8 @@ export function defineStep(stepConfig: StepConfig) {
// and skip/delay registering cucumber steps until cucumber is loaded
const isMissingCucumber = /Cucumber that isn't running/i.test(e.message);
if (isMissingCucumber) {
exitWithMessage(
`Option "importTestFrom" should point to separate file without step definitions`,
exit(
`Option "importTestFrom" should point to a separate file without step definitions`,
`(e.g. without calls of Given, When, Then)`,
);
} else {
Expand Down
Loading

0 comments on commit 9b90209

Please sign in to comment.