Skip to content

Commit

Permalink
feat: simplify wdio configuration inheritence
Browse files Browse the repository at this point in the history
  • Loading branch information
Roozenboom committed Mar 22, 2023
1 parent 4cf73c2 commit a8370c6
Show file tree
Hide file tree
Showing 17 changed files with 484 additions and 122 deletions.
41 changes: 32 additions & 9 deletions packages/webdriverio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,22 +69,45 @@ npx nx e2e your-app-name-e2e --configuration=ci

## Configuration

The @rbnx/webdriverio nx plugin supports two configuration methods, the default configuration option uses the options object in the `project.json` file and will generate the wdio config file when you run the tests.
An alternative method is to only specify the path to the configuration file in the project.json with the `wdioConfig` property. In this case it will not generate a config file but will use the one specified.
The @rbnx/webdriverio nx plugin uses an inheritence configuration model, with a base configuration (`wdio.base.config.ts`) in the root of the NX workspace and configuration on project level (`wdio.config.ts`) that extends the base configuration. The project WebdriverIO configuration path is set in the NX project (`project.json`) e2e target options `wdioConfig` property, the target options can also be used to overwrite the base and project configuration.

**NOTE:** For creating a WebdriverIO e2e project with configuration file you can set the flag `auto-config` to `false`

```sh
npx nx generate @rbnx/webdriverio:project your-app-name --no-auto-config
```json
"targets": {
"e2e": {
"executor": "@rbnx/webdriverio:e2e",
"options": {
"wdioConfig": "wdio.config.ts"
}
}
},
```

Regardless of whether you choose configuration via project.json or wdio config, you can use all standard WebdriverIO configuration options.

For all the WebdriverIO configuration options please check the [official documentation](https://webdriver.io/docs/configurationfile) or the [example wdio config file](https://github.com/webdriverio/webdriverio/blob/main/examples/wdio.conf.js) with all possible options.

### DevServer

To automatically start the devServer before the e2e tests are executed you need to provide the configuration option `devServerTarget`.

If `devServerTarget` is provided, the url returned from the started dev server will be passed to WebdriverIO as the baseUrl option.

```json
"targets": {
"e2e": {
"executor": "@rbnx/webdriverio:e2e",
"options": {
...
"devServerTarget": "your-app-name:serve:development",
...
}
}
},
```

To skip the execution of the devServer you can overwrite this by providing the `--skipServe` flag.

### Capabilities

The @rbnx/webdriverio plugin has some predefined capabilities that can be configured with the `browsers` option. The predefined capabilies are:
The @rbnx/webdriverio plugin has some predefined capabilities that can be configured with the `browsers` option in the project configuration. The predefined capabilies are:

- Chrome
- Firefox
Expand Down
21 changes: 19 additions & 2 deletions packages/webdriverio/src/executors/e2e/executor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe('Build Executor', () => {
it('can run', async () => {
const output = await runExecutor({ wdioConfig: 'wdio.config.ts' }, context);
expect(execMock).toHaveBeenCalledWith(
'npx wdio wdio.config.ts',
'npx wdio wdio.generated.config.ts',
expect.objectContaining({ cwd: './apps/test-e2e' }),
expect.any(Function)
);
Expand Down Expand Up @@ -95,7 +95,7 @@ describe('Build Executor', () => {
context
);
expect(execMock).toHaveBeenCalledWith(
'npx wdio wdio.config.ts --spec=src/e2e/test.spec.ts',
'npx wdio wdio.generated.config.ts --spec=src/e2e/test.spec.ts',
expect.objectContaining({ cwd: './apps/test-e2e' }),
expect.any(Function)
);
Expand All @@ -121,6 +121,23 @@ describe('Build Executor', () => {
expect(output.success).toBe(true);
});

it('should run with specified protocol', async () => {
const output = await runExecutor(
{
specs: ['src/e2e/**/*.spec.ts'],
protocol: 'devtools',
},
context
);
expect(execMock).toHaveBeenCalledWith(
'npx wdio wdio.generated.config.ts',
expect.objectContaining({ cwd: './apps/test-e2e' }),
expect.any(Function)
);
expect(pipe).toHaveBeenCalledTimes(1);
expect(output.success).toBe(true);
});

it('should start dev server', async () => {
const options = {
specs: ['src/e2e/**/*.spec.ts'],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Options } from '@wdio/types';
import { wdioConfig } from '<%= offsetFromRoot %>wdio.base.config';
import { <%= options.baseConfigModuleName %> } from '<%= options.baseConfigPath %>';

export const config: Options.Testrunner = {
...wdioConfig,
Expand Down
34 changes: 30 additions & 4 deletions packages/webdriverio/src/executors/e2e/lib/normalize-options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { ExecutorContext, joinPathFragments } from '@nrwl/devkit';
import {
ExecutorContext,
joinPathFragments,
offsetFromRoot,
} from '@nrwl/devkit';
import Path from 'node:path';
import { capabilitiesFilter } from '../../../wdio';
import type { NormalizedSchema, Schema } from '../schema';

export function normalizeOptions(
Expand All @@ -8,16 +14,36 @@ export function normalizeOptions(
const projectName = context.projectName;
const projectRoot =
context.projectsConfigurations.projects[context.projectName]?.root ?? '';
const configFile = options.wdioConfig
? options.wdioConfig
: 'wdio.generated.config.ts';

const configFile = 'wdio.generated.config.ts';
const configPath = joinPathFragments(projectRoot, configFile);

const baseConfigModuleName = options.wdioConfig
? 'config as wdioConfig'
: 'wdioConfig';
const baseConfigPath = options.wdioConfig
? `./${Path.parse(options.wdioConfig).name}`
: joinPathFragments(offsetFromRoot(projectRoot), 'wdio.base.config');

const isVerbose = context.isVerbose;

if (options.browsers) {
options.capabilities = capabilitiesFilter(options);
}

if (options.protocol) {
options.services = [
...(options.services ?? []),
options.protocol === 'devtools' ? 'devtools' : 'selenium-standalone',
];
}

return {
...options,
configFile,
configPath,
baseConfigModuleName,
baseConfigPath,
isVerbose,
projectName,
projectRoot,
Expand Down
18 changes: 3 additions & 15 deletions packages/webdriverio/src/executors/e2e/lib/run-wdio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@ import {
generateFiles,
getPackageManagerCommand,
joinPathFragments,
offsetFromRoot,
Tree,
workspaceRoot,
} from '@nrwl/devkit';
import { flushChanges, FsTree } from 'nx/src/generators/tree';
import { exec } from 'node:child_process';
import { unlink } from 'node:fs/promises';
import { capabilitiesFilter } from '../../../wdio';
import type { NormalizedSchema } from '../schema';

export async function runWdio(options: NormalizedSchema) {
Expand All @@ -28,23 +26,15 @@ export async function runWdio(options: NormalizedSchema) {
}

export async function generateWdioConfig(options: NormalizedSchema) {
const { wdioConfig, browsers, projectRoot } = options;

if (wdioConfig) return;

if (browsers) {
options.capabilities = capabilitiesFilter(options);
}
const { projectRoot } = options;

const tree: Tree = new FsTree(workspaceRoot, false);
generateFiles(
tree,
joinPathFragments(__dirname, '..', '..', '..', 'wdio', 'files'),
joinPathFragments(__dirname, '..', 'files'),
projectRoot,
{
tmpl: '',
generated: '.generated',
offsetFromRoot: offsetFromRoot(projectRoot),
options,
}
);
Expand All @@ -53,9 +43,7 @@ export async function generateWdioConfig(options: NormalizedSchema) {
}

export async function unlinkWdioConfig(options: NormalizedSchema) {
const { configPath, isVerbose, wdioConfig } = options;

if (wdioConfig) return;
const { configPath, isVerbose } = options;
try {
await unlink(configPath);
} catch (error) {
Expand Down
16 changes: 7 additions & 9 deletions packages/webdriverio/src/executors/e2e/lib/start-dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,22 @@ export async function startDevServer(
options: NormalizedSchema,
context: ExecutorContext
) {
if (!options.devServerTarget || options.skipServe) {
return options.baseUrl;
const { baseUrl, devServerTarget, skipServe } = options;
if (!devServerTarget || skipServe) {
return baseUrl;
}

const devServerTarget = parseTargetString(
options.devServerTarget,
context.projectGraph
);
const target = parseTargetString(devServerTarget, context.projectGraph);

for await (const output of await runExecutor<{
success: boolean;
baseUrl?: string;
}>(devServerTarget, {}, context)) {
}>(target, {}, context)) {
if (!output.success) {
throw new Error(
`Could not start dev server for ${devServerTarget.project} project`
`Could not start dev server for ${target.project} project`
);
}
return options.baseUrl || (output.baseUrl as string);
return baseUrl || (output.baseUrl as string);
}
}
2 changes: 2 additions & 0 deletions packages/webdriverio/src/executors/e2e/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export interface Schema extends WdioOptions {
export interface NormalizedSchema extends Schema {
configFile: string;
configPath: string;
baseConfigModuleName: string;
baseConfigPath: string;
isVerbose: boolean;
projectName: string;
projectRoot: string;
Expand Down
6 changes: 2 additions & 4 deletions packages/webdriverio/src/executors/e2e/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
"properties": {
"specs": {
"type": "array",
"description": "Define which test specs should run. The pattern is relative to the directory of the configuration file being run.",
"default": ["src/e2e/**/*.spec.ts"]
"description": "Define which test specs should run. The pattern is relative to the directory of the configuration file being run."
},
"spec": {
"type": "string",
Expand Down Expand Up @@ -57,8 +56,7 @@
},
"outputDir": {
"type": "string",
"description": "The output directory for the logs, relative to the root configuration of your project.",
"default": "./tmp"
"description": "The output directory for the logs, relative to the root configuration of your project."
},
"bail": {
"type": "number",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import type { Options } from '@wdio/types'

export const wdioConfig: Options.Testrunner = {
//
// ====================
// Runner Configuration
// ====================
// ======================
// Runner Configuration
// ======================
runner: "<%= runner %>",
//
// ============
// Capabilities
// ============
// ==============
// Capabilities
// ==============
// Define your capabilities here. WebdriverIO can run multiple capabilities at the same
// time. Depending on the number of capabilities, WebdriverIO launches several test
// sessions.
Expand All @@ -23,9 +23,9 @@ export const wdioConfig: Options.Testrunner = {
// This is mostly used within CI/CD environments where no display is used.
headless: false,
//
// ===================
// Test Configurations
// ===================
// =====================
// Test Configurations
// =====================
// Define all options that are relevant for the WebdriverIO instance here
//
// Level of logging verbosity: trace | debug | info | warn | error | silent
Expand Down Expand Up @@ -54,9 +54,9 @@ export const wdioConfig: Options.Testrunner = {
// Default request retries count
connectionRetryCount: 3,
//
// =======================
// Framework Configuration
// =======================
// =========================
// Framework Configuration
// =========================
// Framework you want to run your specs with.
// The following are supported: Mocha, Jasmine, and Cucumber
// see also: https://webdriver.io/docs/frameworks
Expand All @@ -78,26 +78,26 @@ export const wdioConfig: Options.Testrunner = {
jasmineOpts: {
// Jasmine default timeout
defaultTimeoutInterval: 60000,
},<% } %>
},<% } %><%
if (services.length) { %>
//
// ======================
// Services Configuration
// ======================
// ========================
// Services Configuration
// ========================
// Test runner services
// Services take over a specific job you don't want to take care of. They enhance
// your test setup with almost no effort. Unlike plugins, they don't add new
// commands. Instead, they hook themselves up into the test process.<%
if (services.length) { %>
// commands. Instead, they hook themselves up into the test process.
services: [<% for (let service of services){ %>'<%= service %>',<% } %>],
<% } %>
<% } %><%
if (reporters.length) { %>
//
// ======================
// Reporter Configuration
// ======================
// ========================
// Reporter Configuration
// ========================
// Test reporter for stdout.
// The only one supported by default is 'dot'
// see also: https://webdriver.io/docs/dot-reporter<%
if (reporters.length) { %>
// see also: https://webdriver.io/docs/dot-reporter
reporters: [<% for (let reporter of reporters){ %>'<%= reporter %>',<% } %>],
<% } %>
};
Loading

0 comments on commit a8370c6

Please sign in to comment.