-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BREAKING CHANGE: - `@nrwl/cypress` no longer required, versions - the plugin is a standalone implementation that is not dependent on `@nrwl/cypress` - use the available options to configure the execution of your cypress runs
- Loading branch information
Showing
8 changed files
with
6,365 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,34 +1,190 @@ | ||
import { ExecutorContext, runExecutor } from '@nrwl/devkit'; | ||
import { patch } from 'cy2'; | ||
import { | ||
ExecutorContext, | ||
logger, | ||
parseTargetString, | ||
readTargetOptions, | ||
runExecutor, | ||
stripIndents, | ||
} from '@nrwl/devkit'; | ||
import 'dotenv/config'; | ||
import { basename, dirname, join } from 'path'; | ||
|
||
export interface CurrentsExecutorOptions { | ||
cypressExecutor: string; | ||
import { installedCypressVersion } from '../utils/cypress-version'; | ||
|
||
import { run } from 'cy2'; | ||
|
||
type CypressOptions = Parameters<typeof run>[1] & { | ||
config?: Partial<{ | ||
baseUrl: string; | ||
}>; | ||
}; | ||
|
||
export type Json = { [k: string]: any }; | ||
|
||
export interface CypressExecutorOptions extends Json { | ||
cypressApiUrl: string; | ||
cypressConfig: string; | ||
watch?: boolean; | ||
tsConfig?: string; | ||
devServerTarget?: string; | ||
key?: string; | ||
record?: boolean; | ||
parallel?: boolean; | ||
baseUrl?: string; | ||
browser?: string; | ||
env?: Record<string, string>; | ||
spec?: string; | ||
ciBuildId?: string | number; | ||
group?: string; | ||
ignoreTestFiles?: string; | ||
reporter?: string; | ||
reporterOptions?: string; | ||
skipServe?: boolean; | ||
testingType?: 'component' | 'e2e'; | ||
tag?: string; | ||
} | ||
|
||
export default async function cypressExecutor( | ||
options: CypressExecutorOptions, | ||
context: ExecutorContext | ||
) { | ||
options = normalizeOptions(options, context); | ||
// this is used by cypress component testing presets to build the executor contexts with the correct configuration options. | ||
process.env.NX_CYPRESS_TARGET_CONFIGURATION = context.configurationName; | ||
let success; | ||
|
||
for await (const baseUrl of startDevServer(options, context)) { | ||
try { | ||
success = await runCypress(baseUrl, options); | ||
if (!options.watch) break; | ||
} catch (e) { | ||
logger.error(e.message); | ||
success = false; | ||
if (!options.watch) break; | ||
} | ||
} | ||
|
||
return { success }; | ||
} | ||
|
||
export default async function currentsExecutor( | ||
options: CurrentsExecutorOptions, | ||
function normalizeOptions( | ||
options: CypressExecutorOptions, | ||
context: ExecutorContext | ||
): CypressExecutorOptions { | ||
options.env = options.env || {}; | ||
if (options.tsConfig) { | ||
const tsConfigPath = join(context.root, options.tsConfig); | ||
options.env.tsConfig = tsConfigPath; | ||
process.env.TS_NODE_PROJECT = tsConfigPath; | ||
} | ||
|
||
warnDeprecatedCypressVersion(); | ||
return options; | ||
} | ||
|
||
function warnDeprecatedCypressVersion() { | ||
if (installedCypressVersion() < 10) { | ||
logger.warn(stripIndents` | ||
NOTE: | ||
Support for Cypress versions < 10 is deprecated. Please upgrade to at least Cypress version 10. | ||
A generator to migrate from v8 to v10 is provided. See https://nx.dev/cypress/v10-migration-guide | ||
`); | ||
} | ||
} | ||
|
||
async function* startDevServer( | ||
opts: CypressExecutorOptions, | ||
context: ExecutorContext | ||
) { | ||
process.env.CYPRESS_API_URL = | ||
options.cypressApiUrl ?? 'https://cy.currents.dev'; | ||
|
||
await patch(); | ||
|
||
const result = await Promise.race([ | ||
await runExecutor( | ||
{ | ||
project: context.projectName, | ||
target: options.cypressExecutor, | ||
configuration: context.configurationName, | ||
}, | ||
{ ...options, watch: false }, | ||
context | ||
), | ||
]); | ||
for await (const res of result) { | ||
if (!res.success) return res; | ||
} | ||
|
||
return { success: true }; | ||
// no dev server, return the provisioned base url | ||
if (!opts.devServerTarget || opts.skipServe) { | ||
yield opts.baseUrl; | ||
return; | ||
} | ||
|
||
const { project, target, configuration } = parseTargetString( | ||
opts.devServerTarget | ||
); | ||
const devServerTargetOpts = readTargetOptions( | ||
{ project, target, configuration }, | ||
context | ||
); | ||
const targetSupportsWatchOpt = | ||
Object.keys(devServerTargetOpts).includes('watch'); | ||
|
||
for await (const output of await runExecutor<{ | ||
success: boolean; | ||
baseUrl?: string; | ||
}>( | ||
{ project, target, configuration }, | ||
// @NOTE: Do not forward watch option if not supported by the target dev server, | ||
// this is relevant for running Cypress against dev server target that does not support this option, | ||
// for instance @nguniversal/builders:ssr-dev-server. | ||
targetSupportsWatchOpt ? { watch: opts.watch } : {}, | ||
context | ||
)) { | ||
if (!output.success && !opts.watch) | ||
throw new Error('Could not compile application files'); | ||
yield opts.baseUrl || (output.baseUrl as string); | ||
} | ||
} | ||
|
||
/** | ||
* Initialize the Cypress test runner with the provided project configuration. | ||
* By default, Cypress will run tests from the CLI without the GUI and provide directly the results in the console output. | ||
*/ | ||
async function runCypress(baseUrl: string, opts: CypressExecutorOptions) { | ||
// Cypress expects the folder where a cypress config is present | ||
const projectFolderPath = dirname(opts.cypressConfig); | ||
const options: CypressOptions = { | ||
project: projectFolderPath, | ||
configFile: basename(opts.cypressConfig), | ||
}; | ||
|
||
// If not, will use the `baseUrl` normally from `cypress.json` | ||
if (baseUrl) { | ||
options.config = { baseUrl }; | ||
} | ||
|
||
if (opts.browser) { | ||
options.browser = opts.browser; | ||
} | ||
|
||
if (opts.env) { | ||
options.env = opts.env; | ||
} | ||
if (opts.spec) { | ||
options.spec = opts.spec; | ||
} | ||
|
||
options.tag = opts.tag; | ||
|
||
options.record = opts.record; | ||
options.key = opts.key; | ||
options.parallel = opts.parallel; | ||
options.ciBuildId = opts.ciBuildId?.toString(); | ||
options.group = opts.group; | ||
|
||
if (opts.reporter) { | ||
options.reporter = opts.reporter; | ||
} | ||
|
||
if (opts.reporterOptions) { | ||
options.reporterOptions = opts.reporterOptions; | ||
} | ||
|
||
options.testingType = opts.testingType; | ||
|
||
const result = await run(opts.cypressApiUrl, options); | ||
|
||
if (result.status === 'failed') { | ||
return false; | ||
} | ||
|
||
return ( | ||
result.runs.reduce( | ||
(acc, run) => acc + (run.stats.failures ?? 0) + (run.stats.skipped ?? 0), | ||
0 | ||
) === 0 | ||
); | ||
} |
Oops, something went wrong.