mirror of
https://github.com/cypress-io/cypress.git
synced 2026-01-06 06:29:45 -06:00
feat: setting up e2e open mode testing framework (#18472)
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
**/build
|
||||
**/cypress/fixtures
|
||||
**/dist
|
||||
**/dist-test
|
||||
**/dist-*
|
||||
**/node_modules
|
||||
**/support/fixtures/*
|
||||
!**/support/fixtures/projects
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -50,6 +50,9 @@ packages/example/app
|
||||
packages/example/build
|
||||
packages/example/cypress/integration
|
||||
|
||||
# from frontend-shared
|
||||
packages/frontend-shared/cypress/e2e/.projects
|
||||
|
||||
# from server
|
||||
packages/server/.cy
|
||||
packages/server/.projects
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"check-ts": "gulp checkTs",
|
||||
"clean-deps": "find . -depth -name node_modules -type d -exec rm -rf {} \\;",
|
||||
"clean-untracked-files": "git clean -d -f",
|
||||
"codegen": "yarn gulp codegen",
|
||||
"debug": "yarn gulp debug",
|
||||
"precypress:open": "yarn ensure-deps",
|
||||
"cypress:open": "cypress open --dev --global",
|
||||
@@ -117,6 +118,7 @@
|
||||
"@types/react": "16.9.50",
|
||||
"@types/react-dom": "16.9.8",
|
||||
"@types/request-promise": "4.1.45",
|
||||
"@types/rimraf": "^3.0.2",
|
||||
"@types/send": "^0.17.1",
|
||||
"@types/sinon-chai": "3.2.3",
|
||||
"@types/through2": "^2.0.36",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../cli/schema/cypress.schema.json",
|
||||
"projectId": "sehy69",
|
||||
"viewportWidth": 800,
|
||||
"viewportHeight": 850,
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
let GQL_PORT
|
||||
let SERVER_PORT
|
||||
|
||||
describe('App', () => {
|
||||
beforeEach(() => {
|
||||
cy.withCtx(async (ctx) => {
|
||||
await ctx.dispose()
|
||||
await ctx.actions.project.setActiveProject(ctx.launchArgs.projectRoot)
|
||||
ctx.actions.wizard.setTestingType('e2e')
|
||||
await ctx.actions.project.initializeActiveProject({
|
||||
skipPluginIntializeForTesting: true,
|
||||
})
|
||||
|
||||
await ctx.actions.project.launchProject('e2e', {
|
||||
skipBrowserOpenForTest: true,
|
||||
})
|
||||
|
||||
return [
|
||||
ctx.gqlServerPort,
|
||||
ctx.appServerPort,
|
||||
]
|
||||
}).then(([gqlPort, serverPort]) => {
|
||||
GQL_PORT = gqlPort
|
||||
SERVER_PORT = serverPort
|
||||
})
|
||||
})
|
||||
|
||||
it('resolves the home page', () => {
|
||||
cy.visit(`dist/index.html?serverPort=${SERVER_PORT}&gqlPort=${GQL_PORT}`)
|
||||
cy.get('[href="#/runner"]').click()
|
||||
cy.get('[href="#/settings"]').click()
|
||||
})
|
||||
|
||||
it('resolves the home page, with a different server port?', () => {
|
||||
cy.visit(`dist/index.html?serverPort=${SERVER_PORT}&gqlPort=${GQL_PORT}`)
|
||||
cy.get('[href="#/runner"]').click()
|
||||
cy.get('[href="#/settings"]').click()
|
||||
})
|
||||
})
|
||||
13
packages/app/cypress/e2e/integration/files.spec.ts
Normal file
13
packages/app/cypress/e2e/integration/files.spec.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
describe('App', () => {
|
||||
beforeEach(() => {
|
||||
cy.setupE2E('component-tests')
|
||||
cy.initializeApp()
|
||||
})
|
||||
|
||||
it('resolves the home page', () => {
|
||||
cy.visitApp()
|
||||
cy.wait(1000)
|
||||
cy.get('[href="#/runner"]').click()
|
||||
cy.get('[href="#/settings"]').click()
|
||||
})
|
||||
})
|
||||
@@ -9,8 +9,9 @@
|
||||
"clean-deps": "rimraf node_modules",
|
||||
"test": "echo 'ok'",
|
||||
"cypress:launch": "cross-env TZ=America/New_York node ../../scripts/cypress open --project ${PWD}",
|
||||
"cypress:open": "yarn gulp cyOpenAppE2E",
|
||||
"cypress:run:e2e": "yarn gulp cyRunAppE2E",
|
||||
"cypress:open": "cross-env TZ=America/New_York node ../../scripts/cypress open --project ${PWD}",
|
||||
"cypress:run:ct": "cross-env TZ=America/New_York node ../../scripts/cypress run-ct --project ${PWD}",
|
||||
"cypress:run:e2e": "cross-env TZ=America/New_York node ../../scripts/cypress run --project ${PWD}",
|
||||
"debug": "gulp debug --project ${PWD}",
|
||||
"dev": "gulp dev --project ${PWD}",
|
||||
"start": "echo \"run 'yarn dev' from the root\" && exit 1",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { DataContext } from '.'
|
||||
import { AppActions, ProjectActions, StorybookActions, WizardActions } from './actions'
|
||||
import { AppActions, FileActions, ProjectActions, StorybookActions, WizardActions } from './actions'
|
||||
import { AuthActions } from './actions/AuthActions'
|
||||
import { DevActions } from './actions/DevActions'
|
||||
import { cached } from './util'
|
||||
@@ -7,6 +7,11 @@ import { cached } from './util'
|
||||
export class DataActions {
|
||||
constructor (private ctx: DataContext) {}
|
||||
|
||||
@cached
|
||||
get file () {
|
||||
return new FileActions(this.ctx)
|
||||
}
|
||||
|
||||
@cached
|
||||
get dev () {
|
||||
return new DevActions(this.ctx)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { LaunchArgs, OpenProjectLaunchOptions, PlatformName } from '@packages/types'
|
||||
import path from 'path'
|
||||
import type { AppApiShape, ProjectApiShape } from './actions'
|
||||
import type { NexusGenAbstractTypeMembers } from '@packages/graphql/src/gen/nxs.gen'
|
||||
import type { AuthApiShape } from './actions/AuthActions'
|
||||
@@ -45,6 +46,11 @@ export class DataContext extends DataContextShell {
|
||||
return fsExtra
|
||||
}
|
||||
|
||||
@cached
|
||||
get path () {
|
||||
return path
|
||||
}
|
||||
|
||||
constructor (private config: DataContextConfig) {
|
||||
super(config)
|
||||
this._coreData = config.coreData ?? makeCoreData()
|
||||
@@ -193,7 +199,9 @@ export class DataContext extends DataContextShell {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
async dispose () {
|
||||
async destroy () {
|
||||
super.destroy()
|
||||
|
||||
return Promise.all([
|
||||
this.util.disposeLoaders(),
|
||||
this.actions.project.clearActiveProject(),
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import type { Server } from 'http'
|
||||
import type { AddressInfo } from 'net'
|
||||
import { DataEmitterActions } from './actions/DataEmitterActions'
|
||||
import { cached } from './util/cached'
|
||||
|
||||
@@ -9,6 +11,7 @@ export interface DataContextShellConfig {
|
||||
// Used in places where we have to create a "shell" data context,
|
||||
// for non-unified parts of the codebase
|
||||
export class DataContextShell {
|
||||
private _gqlServer?: Server
|
||||
private _appServerPort: number | undefined
|
||||
private _gqlServerPort: number | undefined
|
||||
|
||||
@@ -18,8 +21,9 @@ export class DataContextShell {
|
||||
this._appServerPort = port
|
||||
}
|
||||
|
||||
setGqlServerPort (port: number | undefined) {
|
||||
this._gqlServerPort = port
|
||||
setGqlServer (srv: Server) {
|
||||
this._gqlServer = srv
|
||||
this._gqlServerPort = (srv.address() as AddressInfo).port
|
||||
}
|
||||
|
||||
get appServerPort () {
|
||||
@@ -40,4 +44,8 @@ export class DataContextShell {
|
||||
busApi: this.shellConfig.rootBus,
|
||||
}
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this._gqlServer?.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
import path from 'path'
|
||||
|
||||
import type { DataContext } from '..'
|
||||
|
||||
export class FileActions {
|
||||
constructor (private ctx: DataContext) {}
|
||||
|
||||
async writeFileInProject (relativePath: string, data: any) {
|
||||
if (!this.ctx.activeProject) {
|
||||
throw new Error(`Cannot write file in project without active project`)
|
||||
}
|
||||
|
||||
await this.ctx.fs.writeFile(
|
||||
path.join(this.ctx.activeProject?.projectRoot, relativePath),
|
||||
data,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,14 @@ export class ProjectActions {
|
||||
}
|
||||
|
||||
async clearActiveProject () {
|
||||
this.ctx.appData.activeProject = null
|
||||
await this.api.closeActiveProject()
|
||||
|
||||
return this.api.closeActiveProject()
|
||||
// TODO(tim): Improve general state management w/ immutability (immer) & updater fn
|
||||
this.ctx.coreData.app.isInGlobalMode = true
|
||||
this.ctx.coreData.app.activeProject = null
|
||||
this.ctx.coreData.app.activeTestingType = null
|
||||
this.ctx.coreData.wizard.history = ['welcome']
|
||||
this.ctx.coreData.wizard.currentStep = 'welcome'
|
||||
}
|
||||
|
||||
private get projects () {
|
||||
|
||||
9
packages/data-context/src/sources/SettingsDataSource.ts
Normal file
9
packages/data-context/src/sources/SettingsDataSource.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { DataContext } from '..'
|
||||
|
||||
export class SettingsDataSource {
|
||||
constructor (private ctx: DataContext) {}
|
||||
|
||||
readSettingsForProject (projectRoot: string) {
|
||||
this.ctx.util.assertAbsolute(projectRoot)
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,12 @@ export class UtilDataSource {
|
||||
return vals.map((v) => v.status === 'fulfilled' ? v.value : this.ensureError(v.reason))
|
||||
}
|
||||
|
||||
assertAbsolute (val: string) {
|
||||
if (!this.ctx.path.isAbsolute(val)) {
|
||||
throw new Error(`Expected ${val} to be an absolute path`)
|
||||
}
|
||||
}
|
||||
|
||||
ensureError (val: any): Error {
|
||||
return val instanceof Error ? val : new Error(val)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export * from './BrowserDataSource'
|
||||
export * from './FileDataSource'
|
||||
export * from './GitDataSource'
|
||||
export * from './ProjectDataSource'
|
||||
export * from './SettingsDataSource'
|
||||
export * from './StorybookDataSource'
|
||||
export * from './UtilDataSource'
|
||||
export * from './WizardDataSource'
|
||||
|
||||
@@ -1,37 +1,92 @@
|
||||
import type { DataContext } from '@packages/data-context'
|
||||
import * as inspector from 'inspector'
|
||||
import sinonChai from '@cypress/sinon-chai'
|
||||
import sinon from 'sinon'
|
||||
import rimraf from 'rimraf'
|
||||
import util from 'util'
|
||||
|
||||
// require'd so we don't conflict with globals loaded in @packages/types
|
||||
const chai = require('chai')
|
||||
const chaiAsPromised = require('chai-as-promised')
|
||||
const chaiSubset = require('chai-subset')
|
||||
const { expect } = chai
|
||||
|
||||
chai.use(chaiAsPromised)
|
||||
chai.use(chaiSubset)
|
||||
chai.use(sinonChai)
|
||||
|
||||
import path from 'path'
|
||||
import type { WithCtxInjected, WithCtxOptions } from './support/e2eSupport'
|
||||
import { e2eProjectDirs } from './support/e2eProjectDirs'
|
||||
|
||||
export async function e2ePluginSetup (projectRoot: string, on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) {
|
||||
process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF = 'true'
|
||||
// require'd so we don't import the types from @packages/server which would
|
||||
// pollute strict type checking
|
||||
const { runInternalServer } = require('@packages/server/lib/modes/internal-server')
|
||||
const Fixtures = require('../../../server/test/support/helpers/fixtures')
|
||||
const tmpDir = path.join(__dirname, '.projects')
|
||||
|
||||
process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF = 'true'
|
||||
const { serverPortPromise, ctx } = runInternalServer({
|
||||
projectRoot,
|
||||
}) as {ctx: DataContext, serverPortPromise: Promise<number>}
|
||||
await util.promisify(rimraf)(tmpDir)
|
||||
|
||||
Fixtures.setTmpDir(tmpDir)
|
||||
|
||||
interface WithCtxObj {
|
||||
fn: string
|
||||
options: WithCtxOptions
|
||||
activeTestId: string
|
||||
}
|
||||
|
||||
let ctx: DataContext
|
||||
let serverPortPromise: Promise<number>
|
||||
let currentTestId: string | undefined
|
||||
let testState: Record<string, any> = {}
|
||||
|
||||
on('task', {
|
||||
async withCtx (fnString: string) {
|
||||
await serverPortPromise
|
||||
setupE2E (projectName: string) {
|
||||
Fixtures.scaffoldProject(projectName)
|
||||
|
||||
return new Function('ctx', `return (${fnString})(ctx)`).call(undefined, ctx)
|
||||
return null
|
||||
},
|
||||
async resetCtxState () {
|
||||
return ctx.dispose()
|
||||
},
|
||||
async visitLaunchpad () {
|
||||
async withCtx (obj: WithCtxObj) {
|
||||
// Ensure we spin up a completely isolated server/state for each test
|
||||
if (obj.activeTestId !== currentTestId) {
|
||||
ctx?.destroy()
|
||||
currentTestId = obj.activeTestId
|
||||
testState = {};
|
||||
({ serverPortPromise, ctx } = runInternalServer({
|
||||
projectRoot: null,
|
||||
}) as {ctx: DataContext, serverPortPromise: Promise<number>})
|
||||
|
||||
},
|
||||
async visitApp () {
|
||||
await serverPortPromise
|
||||
}
|
||||
|
||||
},
|
||||
getGraphQLPort () {
|
||||
return serverPortPromise
|
||||
},
|
||||
getAppServerPort () {
|
||||
return ctx.appServerPort ?? null
|
||||
const options: WithCtxInjected = {
|
||||
...obj.options,
|
||||
testState,
|
||||
require,
|
||||
process,
|
||||
projectDir (projectName) {
|
||||
if (!e2eProjectDirs.includes(projectName)) {
|
||||
throw new Error(`${projectName} is not a fixture project`)
|
||||
}
|
||||
|
||||
return path.join(tmpDir, projectName)
|
||||
},
|
||||
}
|
||||
|
||||
const val = await Promise.resolve(new Function('ctx', 'options', 'chai', 'expect', 'sinon', `
|
||||
return (${obj.fn})(ctx, options, chai, expect, sinon)
|
||||
`).call(undefined, ctx, options, chai, expect, sinon))
|
||||
|
||||
return val || null
|
||||
},
|
||||
})
|
||||
|
||||
return config
|
||||
return {
|
||||
...config,
|
||||
env: {
|
||||
e2e_isDebugging: Boolean(inspector.url()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/* eslint-disable */
|
||||
// Auto-generated by gulpE2ETestScaffold.ts
|
||||
export const e2eProjectDirs = [
|
||||
'browser-extensions',
|
||||
'busted-support-file',
|
||||
'chrome-browser-preferences',
|
||||
'component-tests',
|
||||
'config-with-custom-file-js',
|
||||
'config-with-custom-file-ts',
|
||||
'config-with-invalid-browser',
|
||||
'config-with-invalid-viewport',
|
||||
'config-with-js',
|
||||
'config-with-short-timeout',
|
||||
'config-with-ts',
|
||||
'cookies',
|
||||
'default-layout',
|
||||
'downloads',
|
||||
'e2e',
|
||||
'empty-folders',
|
||||
'failures',
|
||||
'firefox-memory',
|
||||
'fixture-subfolder-of-integration',
|
||||
'folder-same-as-fixture',
|
||||
'hooks-after-rerun',
|
||||
'ids',
|
||||
'integration-outside-project-root',
|
||||
'issue-8111-iframe-input',
|
||||
'max-listeners',
|
||||
'multiple-task-registrations',
|
||||
'no-scaffolding',
|
||||
'no-server',
|
||||
'non-existent-spec',
|
||||
'non-proxied',
|
||||
'odd-directory-name',
|
||||
'plugin-after-screenshot',
|
||||
'plugin-after-spec-deletes-video',
|
||||
'plugin-before-browser-launch-deprecation',
|
||||
'plugin-browser',
|
||||
'plugin-config',
|
||||
'plugin-config-version',
|
||||
'plugin-empty',
|
||||
'plugin-event-deprecated',
|
||||
'plugin-extension',
|
||||
'plugin-filter-browsers',
|
||||
'plugin-retries',
|
||||
'plugin-returns-bad-config',
|
||||
'plugin-returns-empty-browsers-list',
|
||||
'plugin-returns-invalid-browser',
|
||||
'plugin-run-event-throws',
|
||||
'plugin-run-events',
|
||||
'plugin-validation-error',
|
||||
'plugins-absolute-path',
|
||||
'plugins-async-error',
|
||||
'plugins-root-async-error',
|
||||
'pristine',
|
||||
'read-only-project-root',
|
||||
'record',
|
||||
'remote-debugging-disconnect',
|
||||
'remote-debugging-port-removed',
|
||||
'retries-2',
|
||||
'same-fixtures-integration-folders',
|
||||
'screen-size',
|
||||
'server',
|
||||
'shadow-dom-global-inclusion',
|
||||
'studio',
|
||||
'studio-no-source-maps',
|
||||
'system-node',
|
||||
'task-not-registered',
|
||||
'todos',
|
||||
'ts-installed',
|
||||
'ts-proj',
|
||||
'ts-proj-custom-names',
|
||||
'ts-proj-esmoduleinterop-true',
|
||||
'ts-proj-tsconfig-in-plugins',
|
||||
'ts-proj-with-module-esnext',
|
||||
'ts-proj-with-paths',
|
||||
'uncaught-support-file',
|
||||
'unify-onboarding',
|
||||
'unify-plugin-errors',
|
||||
'various-file-types',
|
||||
'webpack-preprocessor',
|
||||
'webpack-preprocessor-awesome-typescript-loader',
|
||||
'webpack-preprocessor-ts-loader',
|
||||
'webpack-preprocessor-ts-loader-compiler-options',
|
||||
'working-preprocessor',
|
||||
'yarn-v2-pnp'
|
||||
] as const
|
||||
@@ -1,17 +1,125 @@
|
||||
import '@testing-library/cypress/add-commands'
|
||||
import type { DataContext } from '@packages/data-context'
|
||||
import { e2eProjectDirs } from './e2eProjectDirs'
|
||||
|
||||
const SIXTY_SECONDS = 60 * 1000
|
||||
const NO_TIMEOUT = 1000 * 1000
|
||||
const FOUR_SECONDS = 4 * 1000
|
||||
|
||||
export type ProjectFixture = typeof e2eProjectDirs[number]
|
||||
|
||||
export interface WithCtxOptions extends Cypress.Loggable, Cypress.Timeoutable {
|
||||
projectName?: ProjectFixture
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface WithCtxInjected extends WithCtxOptions {
|
||||
require: typeof require
|
||||
process: typeof process
|
||||
testState: Record<string, any>
|
||||
projectDir(projectName: ProjectFixture): string
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
withCtx(fn: (ctx: DataContext) => any): Chainable
|
||||
/**
|
||||
* Calls a function block with the "ctx" object from the server,
|
||||
* and an object containing any options passed into the server context
|
||||
* and some helper properties:
|
||||
*
|
||||
*
|
||||
* You cannot access any variables outside of the function scope,
|
||||
* however we do provide expect, chai, sinon
|
||||
*/
|
||||
withCtx: typeof withCtx
|
||||
/**
|
||||
* Takes the name of a "system" test directory, and mounts the project within open mode
|
||||
*/
|
||||
setupE2E: typeof setupE2E
|
||||
initializeApp: typeof initializeApp
|
||||
visitApp(href?: string): Chainable<string>
|
||||
visitLaunchpad(href?: string): Chainable<string>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add('withCtx', (fn) => {
|
||||
const _log = Cypress.log({
|
||||
beforeEach(() => {
|
||||
// Reset the ports so we know we need to call "setupE2E" before each test
|
||||
Cypress.env('e2e_serverPort', undefined)
|
||||
Cypress.env('e2e_gqlPort', undefined)
|
||||
})
|
||||
|
||||
// function setup
|
||||
|
||||
function setupE2E (projectName?: ProjectFixture) {
|
||||
if (projectName && !e2eProjectDirs.includes(projectName)) {
|
||||
throw new Error(`Unknown project ${projectName}`)
|
||||
}
|
||||
|
||||
if (projectName) {
|
||||
cy.task('setupE2E', projectName, { log: false })
|
||||
}
|
||||
|
||||
return cy.withCtx(async (ctx, o) => {
|
||||
if (o.projectName) {
|
||||
await ctx.actions.project.setActiveProject(o.projectDir(o.projectName))
|
||||
}
|
||||
|
||||
return [
|
||||
ctx.gqlServerPort,
|
||||
ctx.appServerPort,
|
||||
]
|
||||
}, { projectName, log: false }).then(([gqlPort, serverPort]) => {
|
||||
Cypress.env('e2e_gqlPort', gqlPort)
|
||||
Cypress.env('e2e_serverPort', serverPort)
|
||||
})
|
||||
}
|
||||
|
||||
function initializeApp (mode: 'component' | 'e2e' = 'e2e') {
|
||||
return cy.withCtx(async (ctx, o) => {
|
||||
ctx.actions.wizard.setTestingType(o.mode)
|
||||
await ctx.actions.project.initializeActiveProject({
|
||||
skipPluginIntializeForTesting: true,
|
||||
})
|
||||
|
||||
await ctx.actions.project.launchProject(o.mode, {
|
||||
skipBrowserOpenForTest: true,
|
||||
})
|
||||
|
||||
return ctx.appServerPort
|
||||
}, { log: false, mode }).then((serverPort) => {
|
||||
Cypress.env('e2e_serverPort', serverPort)
|
||||
})
|
||||
}
|
||||
|
||||
function visitApp () {
|
||||
const { e2e_serverPort, e2e_gqlPort } = Cypress.env()
|
||||
|
||||
if (!e2e_gqlPort) {
|
||||
throw new Error(`Missing gqlPort - did you forget to call cy.setupE2E(...) ?`)
|
||||
}
|
||||
|
||||
if (!e2e_serverPort) {
|
||||
throw new Error(`Missing serverPort - did you forget to call cy.initializeApp(...) ?`)
|
||||
}
|
||||
|
||||
return cy.visit(`dist-app/index.html?gqlPort=${e2e_gqlPort}&serverPort=${e2e_serverPort}`)
|
||||
}
|
||||
|
||||
function visitLaunchpad (hash?: string) {
|
||||
const { e2e_gqlPort } = Cypress.env()
|
||||
|
||||
if (!e2e_gqlPort) {
|
||||
throw new Error(`Missing gqlPort - did you forget to call cy.setupE2E(...) ?`)
|
||||
}
|
||||
|
||||
cy.visit(`dist-launchpad/index.html?gqlPort=${e2e_gqlPort}`)
|
||||
}
|
||||
|
||||
const pageLoadId = `uid${Math.random()}`
|
||||
|
||||
function withCtx<T extends Partial<WithCtxOptions>> (fn: (ctx: DataContext, o: T & WithCtxInjected) => any, opts: T = {} as T): Cypress.Chainable {
|
||||
const _log = opts.log === false ? { end () {} } : Cypress.log({
|
||||
name: 'withCtx',
|
||||
message: '(view in console)',
|
||||
consoleProps () {
|
||||
@@ -21,7 +129,20 @@ Cypress.Commands.add('withCtx', (fn) => {
|
||||
},
|
||||
})
|
||||
|
||||
cy.task('withCtx', fn.toString(), { timeout: SIXTY_SECONDS, log: false }).then(() => {
|
||||
const { log, timeout, ...rest } = opts
|
||||
|
||||
return cy.task('withCtx', {
|
||||
fn: fn.toString(),
|
||||
options: rest,
|
||||
// @ts-expect-error
|
||||
activeTestId: `${pageLoadId}-${Cypress.mocha.getRunner().test.id ?? Cypress.currentTest.title}`,
|
||||
}, { timeout: timeout ?? Cypress.env('e2e_isDebugging') ? NO_TIMEOUT : FOUR_SECONDS, log }).then(() => {
|
||||
_log.end()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Cypress.Commands.add('visitApp', visitApp)
|
||||
Cypress.Commands.add('visitLaunchpad', visitLaunchpad)
|
||||
Cypress.Commands.add('initializeApp', initializeApp)
|
||||
Cypress.Commands.add('setupE2E', setupE2E)
|
||||
Cypress.Commands.add('withCtx', withCtx)
|
||||
|
||||
@@ -12,6 +12,7 @@ import { client } from '@packages/socket/lib/browser'
|
||||
|
||||
import { cacheExchange as graphcacheExchange } from '@urql/exchange-graphcache'
|
||||
import { pubSubExchange } from './urqlExchangePubsub'
|
||||
import { namedRouteExchange } from './urqlExchangeNamedRoute'
|
||||
|
||||
const GQL_PORT_MATCH = /gqlPort=(\d+)/.exec(window.location.search)
|
||||
const SERVER_PORT_MATCH = /serverPort=(\d+)/.exec(window.location.search)
|
||||
@@ -69,6 +70,7 @@ export function makeUrqlClient (target: 'launchpad' | 'app'): Client {
|
||||
}),
|
||||
// https://formidable.com/open-source/urql/docs/graphcache/errors/
|
||||
makeCacheExchange(),
|
||||
namedRouteExchange,
|
||||
// TODO(tim): add this when we want to use the socket as the GraphQL
|
||||
// transport layer for all operations
|
||||
// target === 'launchpad' ? fetchExchange : socketExchange(io),
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Exchange, getOperationName } from '@urql/core'
|
||||
import { map, pipe } from 'wonka'
|
||||
|
||||
export const namedRouteExchange: Exchange = ({ client, forward }) => {
|
||||
return (ops$) => {
|
||||
return forward(pipe(
|
||||
ops$,
|
||||
map((o) => {
|
||||
return {
|
||||
...o,
|
||||
context: {
|
||||
...o.context,
|
||||
url: `${o.context.url}/${o.kind}-${getOperationName(o.query)}`,
|
||||
},
|
||||
}
|
||||
}),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -56,8 +56,8 @@ export const mutation = mutationType({
|
||||
|
||||
t.nonNull.field('clearActiveProject', {
|
||||
type: 'Query',
|
||||
resolve: (root, args, ctx) => {
|
||||
ctx.actions.project.clearActiveProject()
|
||||
resolve: async (root, args, ctx) => {
|
||||
await ctx.actions.project.clearActiveProject()
|
||||
|
||||
return {}
|
||||
},
|
||||
|
||||
@@ -78,4 +78,8 @@ export const Project = objectType({
|
||||
resolve: (source, args, ctx) => ctx.storybook.loadStorybookInfo(),
|
||||
})
|
||||
},
|
||||
sourceType: {
|
||||
module: __dirname,
|
||||
export: 'ProjectShape',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@ import { parse } from 'graphql'
|
||||
const SHOW_GRAPHIQL = process.env.CYPRESS_INTERNAL_ENV !== 'production'
|
||||
|
||||
export function addGraphQLHTTP (app: ReturnType<typeof express>, context: DataContext) {
|
||||
app.use('/graphql', graphqlHTTP((req, res, params) => {
|
||||
app.use('/graphql/:operationName?', graphqlHTTP((req, res, params) => {
|
||||
const ctx = SHOW_GRAPHIQL ? maybeProxyContext(params, context) : context
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../cli/schema/cypress.schema.json",
|
||||
"projectId": "sehy69",
|
||||
"viewportWidth": 800,
|
||||
"viewportHeight": 850,
|
||||
@@ -19,6 +20,8 @@
|
||||
"pluginsFile": "cypress/component/plugins/index.js"
|
||||
},
|
||||
"e2e": {
|
||||
"supportFile": false
|
||||
"supportFile": "cypress/e2e/support/e2eSupport.ts",
|
||||
"integrationFolder": "cypress/e2e/integration",
|
||||
"pluginsFile": "cypress/e2e/plugins/index.ts"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
describe('Onboarding Flow', () => {
|
||||
beforeEach(() => {
|
||||
cy.setupE2E('unify-onboarding')
|
||||
})
|
||||
|
||||
it('can scaffold a project in e2e mode', () => {
|
||||
cy.visitLaunchpad()
|
||||
cy.get('[data-cy-testingType=component]').click()
|
||||
cy.get('[data-cy=select-framework]').click()
|
||||
cy.get('[data-cy-framework=vue]').click()
|
||||
cy.get('[data-cy=select-framework]').should('contain', 'Vue')
|
||||
cy.get('[data-cy=select-bundler]').click()
|
||||
cy.get('[data-cy-bundler=webpack]').click()
|
||||
cy.get('[data-cy=select-bundler]').should('contain', 'Webpack')
|
||||
cy.reload()
|
||||
cy.get('[data-cy=select-framework]').should('contain', 'Vue')
|
||||
cy.get('[data-cy=select-bundler]').should('contain', 'Webpack')
|
||||
cy.findByText('Next Step').click()
|
||||
cy.get('h1').should('contain', 'Dependencies')
|
||||
})
|
||||
})
|
||||
20
packages/launchpad/cypress/e2e/integration/open-mode.spec.ts
Normal file
20
packages/launchpad/cypress/e2e/integration/open-mode.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
describe('Launchpad: Open Mode', () => {
|
||||
beforeEach(() => {
|
||||
cy.setupE2E()
|
||||
cy.visitLaunchpad()
|
||||
})
|
||||
|
||||
it('Shows the open page', () => {
|
||||
cy.get('h1').should('contain', 'Cypress')
|
||||
})
|
||||
|
||||
it('allows adding a project', () => {
|
||||
cy.withCtx(async (ctx, o) => {
|
||||
await ctx.actions.project.setActiveProject(o.projectDir('todos'))
|
||||
ctx.emitter.toLaunchpad()
|
||||
})
|
||||
|
||||
cy.get('h1').should('contain', 'Welcome to Cypress')
|
||||
cy.findByText('Choose which method of testing you would like to set up first.')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
describe('Plugin error handling', () => {
|
||||
it('it handles a plugin error', () => {
|
||||
cy.setupE2E('unify-plugin-errors')
|
||||
cy.visitLaunchpad()
|
||||
// TODO(alejandro): use this to test against error flow
|
||||
cy.get('[data-cy-testingType=e2e]').click()
|
||||
cy.wait(2000)
|
||||
|
||||
cy.withCtx((ctx) => {
|
||||
ctx.actions.file.writeFileInProject('cypress/plugins/index.js', `module.exports = (on, config) => {}`)
|
||||
})
|
||||
|
||||
cy.reload()
|
||||
cy.wait(2000)
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
/// <reference types="cypress" />
|
||||
const { monorepoPaths } = require('../../../../scripts/gulp/monorepoPaths')
|
||||
const { monorepoPaths } = require('../../../../../scripts/gulp/monorepoPaths')
|
||||
import { e2ePluginSetup } from '@packages/frontend-shared/cypress/e2e/e2ePluginSetup'
|
||||
|
||||
// ***********************************************************
|
||||
2
packages/launchpad/cypress/e2e/support/e2eSupport.ts
Normal file
2
packages/launchpad/cypress/e2e/support/e2eSupport.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference path="../../../../frontend-shared/cypress/e2e/support/e2eSupport.ts" />
|
||||
require('../../../../frontend-shared/cypress/e2e/support/e2eSupport')
|
||||
@@ -1,14 +0,0 @@
|
||||
let GQL_PORT
|
||||
|
||||
describe('Launchpad', () => {
|
||||
before(() => {
|
||||
cy.task('getGraphQLPort').then((port) => {
|
||||
GQL_PORT = port
|
||||
})
|
||||
})
|
||||
|
||||
it('resolves the home page', () => {
|
||||
cy.visit(`dist/index.html?gqlPort=${GQL_PORT}`)
|
||||
cy.get('h1').should('contain', 'Welcome')
|
||||
})
|
||||
})
|
||||
@@ -10,9 +10,9 @@
|
||||
"test": "yarn cypress:run:ct && yarn types",
|
||||
"windi": "yarn windicss-analysis",
|
||||
"cypress:launch": "cross-env TZ=America/New_York node ../../scripts/cypress open --project ${PWD}",
|
||||
"cypress:open": "yarn gulp cyOpenLaunchpadE2E",
|
||||
"cypress:open": "cross-env TZ=America/New_York node ../../scripts/cypress open --project ${PWD}",
|
||||
"cypress:run:ct": "cross-env TZ=America/New_York node ../../scripts/cypress run-ct --project ${PWD}",
|
||||
"cypress:run:e2e": "yarn gulp cyRunLaunchpadE2E",
|
||||
"cypress:run:e2e": "cross-env TZ=America/New_York node ../../scripts/cypress run --project ${PWD}",
|
||||
"dev": "yarn gulp dev --project ${PWD}",
|
||||
"start": "echo 'run yarn dev from the root' && exit 1",
|
||||
"watch": "echo 'run yarn dev from the root' && exit 1"
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
w-full
|
||||
focus:border-indigo-600 focus:outline-transparent
|
||||
"
|
||||
data-cy="select-bundler"
|
||||
:class="disabledClass
|
||||
+ (isOpen ? ' border-indigo-600' : ' border-gray-200')
|
||||
+ (props.disabled ? ' bg-gray-100 text-gray-800' : '')"
|
||||
@@ -68,6 +69,7 @@
|
||||
:key="opt.type"
|
||||
focus="1"
|
||||
class="cursor-pointer flex items-center py-1 px-2 hover:bg-gray-10"
|
||||
:data-cy-bundler="opt.type"
|
||||
@click="selectOption(opt.type)"
|
||||
>
|
||||
<img
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
:key="opt.id"
|
||||
focus="1"
|
||||
class="cursor-pointer flex items-center py-1 px-2 hover:bg-gray-10"
|
||||
:data-cy-framework="opt.type"
|
||||
@click="selectOption(opt.type)"
|
||||
>
|
||||
<img
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<TestingTypeCard
|
||||
v-if="ct"
|
||||
:id="ct.type"
|
||||
:data-cy-testingType="ct.type"
|
||||
:title="ct.title"
|
||||
:description="firstTimeCT ? ct.description : 'LAUNCH'"
|
||||
:configured="!firstTimeCT"
|
||||
@@ -15,6 +16,7 @@
|
||||
<TestingTypeCard
|
||||
v-if="e2e"
|
||||
:id="e2e.type"
|
||||
:data-cy-testingType="e2e.type"
|
||||
:title="e2e.title"
|
||||
:description="firstTimeE2E ? e2e.description : 'LAUNCH'"
|
||||
:configured="!firstTimeE2E"
|
||||
|
||||
@@ -4,31 +4,8 @@ import type { AddressInfo } from 'net'
|
||||
import type { DataContext } from '@packages/data-context'
|
||||
import pDefer from 'p-defer'
|
||||
import cors from 'cors'
|
||||
import type { Server } from 'http'
|
||||
import { SocketIOServer } from '@packages/socket'
|
||||
|
||||
let graphqlServer: Server | undefined
|
||||
|
||||
export async function closeGraphQLServer () {
|
||||
if (!graphqlServer) {
|
||||
return
|
||||
}
|
||||
|
||||
const dfd = pDefer()
|
||||
|
||||
graphqlServer.close((err) => {
|
||||
if (err) {
|
||||
dfd.reject()
|
||||
}
|
||||
|
||||
dfd.resolve()
|
||||
})
|
||||
|
||||
graphqlServer = undefined
|
||||
|
||||
dfd.promise
|
||||
}
|
||||
|
||||
export async function makeGraphQLServer (ctx: DataContext) {
|
||||
const dfd = pDefer<number>()
|
||||
const app = express()
|
||||
@@ -39,7 +16,7 @@ export async function makeGraphQLServer (ctx: DataContext) {
|
||||
// it's not jammed into the projects
|
||||
addGraphQLHTTP(app, ctx)
|
||||
|
||||
const srv = graphqlServer = app.listen(() => {
|
||||
const srv = app.listen(() => {
|
||||
const port = (srv.address() as AddressInfo).port
|
||||
|
||||
const endpoint = `http://localhost:${port}/graphql`
|
||||
@@ -51,6 +28,8 @@ export async function makeGraphQLServer (ctx: DataContext) {
|
||||
|
||||
ctx.debug(`GraphQL Server at ${endpoint}`)
|
||||
|
||||
ctx.setGqlServer(srv)
|
||||
|
||||
dfd.resolve(port)
|
||||
})
|
||||
|
||||
|
||||
@@ -23,9 +23,5 @@ export function runInternalServer (options) {
|
||||
|
||||
const serverPortPromise = makeGraphQLServer(ctx)
|
||||
|
||||
serverPortPromise.then((port) => {
|
||||
ctx.setGqlServerPort(port)
|
||||
})
|
||||
|
||||
return { ctx, bus, serverPortPromise }
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import type { LaunchOpts, LaunchArgs, OpenProjectLaunchOptions, FoundBrowser } f
|
||||
import { fs } from './util/fs'
|
||||
import path from 'path'
|
||||
import os from 'os'
|
||||
import { closeGraphQLServer } from './gui/makeGraphQLServer'
|
||||
import type { DataContextShell } from '@packages/data-context/src/DataContextShell'
|
||||
|
||||
const debug = Debug('cypress:server:open_project')
|
||||
|
||||
@@ -408,12 +408,15 @@ export class OpenProject {
|
||||
this.stopSpecsWatcher()
|
||||
|
||||
return Promise.all([
|
||||
closeGraphQLServer(),
|
||||
this._ctx?.destroy(),
|
||||
this.closeOpenProjectAndBrowsers(),
|
||||
]).then(() => null)
|
||||
}
|
||||
|
||||
_ctx?: DataContextShell
|
||||
|
||||
async create (path: string, args: LaunchArgs, options: OpenProjectLaunchOptions, browsers: FoundBrowser[] = []) {
|
||||
this._ctx = options.ctx
|
||||
debug('open_project create %s', path)
|
||||
|
||||
_.defaults(options, {
|
||||
|
||||
@@ -94,10 +94,6 @@ module.exports = {
|
||||
// allow overriding the app_data folder
|
||||
let folder = env.CYPRESS_KONFIG_ENV || env.CYPRESS_INTERNAL_ENV
|
||||
|
||||
if (env.CYPRESS_INTERNAL_E2E_TESTING_SELF) {
|
||||
folder = `${folder}-e2e-test`
|
||||
}
|
||||
|
||||
const p = path.join(ELECTRON_APP_DATA_PATH, 'cy', folder, ...paths)
|
||||
|
||||
log('path: %s', p)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,3 @@
|
||||
describe('unify-plugin-errors', () => {
|
||||
it('ok', () => {})
|
||||
})
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = async function () {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
|
||||
throw new Error('Error Loading Plugin!!!')
|
||||
}
|
||||
@@ -4,7 +4,7 @@ const chokidar = require('chokidar')
|
||||
|
||||
const root = path.join(__dirname, '..', '..', '..')
|
||||
const projects = path.join(root, 'test', 'support', 'fixtures', 'projects')
|
||||
const tmpDir = path.join(root, '.projects')
|
||||
let tmpDir = path.join(root, '.projects')
|
||||
|
||||
// copy contents instead of deleting+creating new file, which can cause
|
||||
// filewatchers to lose track of toFile.
|
||||
@@ -22,6 +22,14 @@ const copyContents = (fromFile, toFile) => {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setTmpDir (dir) {
|
||||
tmpDir = dir
|
||||
},
|
||||
|
||||
scaffoldProject (project) {
|
||||
return fs.copySync(path.join(projects, project), path.join(tmpDir, project))
|
||||
},
|
||||
|
||||
// copies all of the project fixtures
|
||||
// to the tmpDir .projects in the root
|
||||
scaffold () {
|
||||
|
||||
@@ -11,7 +11,7 @@ import gulp from 'gulp'
|
||||
import { autobarrelWatcher } from './tasks/gulpAutobarrel'
|
||||
import { startCypressWatch, openCypressLaunchpad, openCypressApp, runCypressLaunchpad, wrapRunWithExit, runCypressApp, killExistingCypress } from './tasks/gulpCypress'
|
||||
import { graphqlCodegen, graphqlCodegenWatch, nexusCodegen, nexusCodegenWatch, generateFrontendSchema, syncRemoteGraphQL } from './tasks/gulpGraphql'
|
||||
import { viteApp, viteCleanApp, viteCleanLaunchpad, viteLaunchpad, viteBuildApp, viteBuildAndWatchApp, viteBuildLaunchpad, viteBuildAndWatchLaunchpad } from './tasks/gulpVite'
|
||||
import { viteApp, viteCleanApp, viteCleanLaunchpad, viteLaunchpad, viteBuildApp, viteBuildAndWatchApp, viteBuildLaunchpad, viteBuildAndWatchLaunchpad, symlinkViteProjects } from './tasks/gulpVite'
|
||||
import { checkTs } from './tasks/gulpTsc'
|
||||
import { makePathMap } from './utils/makePathMap'
|
||||
import { makePackage } from './tasks/gulpMakePackage'
|
||||
@@ -19,6 +19,7 @@ import { setGulpGlobal } from './gulpConstants'
|
||||
import { exitAfterAll } from './tasks/gulpRegistry'
|
||||
import { execSync } from 'child_process'
|
||||
import { webpackRunner } from './tasks/gulpWebpack'
|
||||
import { e2eTestScaffold, e2eTestScaffoldWatch } from './tasks/gulpE2ETestScaffold'
|
||||
|
||||
/**------------------------------------------------------------------------
|
||||
* Local Development Workflow
|
||||
@@ -46,6 +47,9 @@ gulp.task(
|
||||
|
||||
// ... and generate the correct GraphQL types for the frontend
|
||||
graphqlCodegenWatch,
|
||||
|
||||
// ... and generate the patsh for the e2e support file watching
|
||||
e2eTestScaffoldWatch,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -71,6 +75,7 @@ gulp.task(
|
||||
viteApp,
|
||||
viteLaunchpad,
|
||||
webpackRunner,
|
||||
e2eTestScaffoldWatch,
|
||||
),
|
||||
|
||||
// And we're finally ready for electron, watching for changes in
|
||||
@@ -123,6 +128,7 @@ gulp.task('buildProd',
|
||||
viteBuildApp,
|
||||
viteBuildLaunchpad,
|
||||
),
|
||||
symlinkViteProjects,
|
||||
))
|
||||
|
||||
gulp.task(
|
||||
@@ -133,6 +139,19 @@ gulp.task(
|
||||
),
|
||||
)
|
||||
|
||||
gulp.task('watchForE2E', gulp.series(
|
||||
'codegen',
|
||||
gulp.parallel(
|
||||
gulp.series(
|
||||
viteBuildAndWatchLaunchpad,
|
||||
viteBuildAndWatchApp,
|
||||
),
|
||||
webpackRunner,
|
||||
),
|
||||
symlinkViteProjects,
|
||||
e2eTestScaffold,
|
||||
))
|
||||
|
||||
/**------------------------------------------------------------------------
|
||||
* Launchpad Testing
|
||||
* This task builds and hosts the launchpad as if it was a static website.
|
||||
@@ -176,10 +195,12 @@ const cyOpenLaunchpad = gulp.series(
|
||||
// This watches for changes and is not the same things as statically
|
||||
// building the app for production.
|
||||
gulp.parallel(
|
||||
viteBuildApp,
|
||||
viteBuildAndWatchLaunchpad,
|
||||
viteBuildApp,
|
||||
),
|
||||
|
||||
symlinkViteProjects,
|
||||
|
||||
// 2. Start the REAL (dev) Cypress App, which will launch in open mode.
|
||||
openCypressLaunchpad,
|
||||
)
|
||||
@@ -196,6 +217,8 @@ const cyOpenApp = gulp.series(
|
||||
webpackRunner,
|
||||
),
|
||||
|
||||
symlinkViteProjects,
|
||||
|
||||
// 2. Start the REAL (dev) Cypress App, which will launch in open mode.
|
||||
openCypressApp,
|
||||
)
|
||||
@@ -242,6 +265,7 @@ gulp.task(makePackage)
|
||||
* here for debugging, e.g. `yarn gulp syncRemoteGraphQL`
|
||||
*------------------------------------------------------------------------**/
|
||||
|
||||
gulp.task(symlinkViteProjects)
|
||||
gulp.task(syncRemoteGraphQL)
|
||||
gulp.task(generateFrontendSchema)
|
||||
gulp.task(makePathMap)
|
||||
@@ -265,6 +289,8 @@ gulp.task('debugCypressLaunchpad', gulp.series(
|
||||
openCypressLaunchpad,
|
||||
))
|
||||
|
||||
gulp.task(e2eTestScaffoldWatch)
|
||||
gulp.task(e2eTestScaffold)
|
||||
gulp.task(startCypressWatch)
|
||||
gulp.task(openCypressApp)
|
||||
gulp.task(openCypressLaunchpad)
|
||||
|
||||
52
scripts/gulp/tasks/gulpE2ETestScaffold.ts
Normal file
52
scripts/gulp/tasks/gulpE2ETestScaffold.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import chokidar from 'chokidar'
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
import { monorepoPaths } from '../monorepoPaths'
|
||||
|
||||
const PROJECT_FIXTURE_DIRECTORY = 'test/support/fixtures/projects'
|
||||
|
||||
const DIR_PATH = path.join(monorepoPaths.pkgServer, PROJECT_FIXTURE_DIRECTORY)
|
||||
const OUTPUT_PATH = path.join(monorepoPaths.pkgFrontendShared, 'cypress/e2e/support/e2eProjectDirs.ts')
|
||||
|
||||
export async function e2eTestScaffold () {
|
||||
const possibleDirectories = await fs.readdir(DIR_PATH)
|
||||
const dirs = await Promise.all(possibleDirectories.map(async (dir) => {
|
||||
const fullPath = path.join(DIR_PATH, dir)
|
||||
const stat = await fs.stat(fullPath)
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
return fullPath
|
||||
}
|
||||
}))
|
||||
const allDirs = dirs.filter((dir) => dir) as string[]
|
||||
|
||||
await fs.writeFile(
|
||||
OUTPUT_PATH,
|
||||
`/* eslint-disable */
|
||||
// Auto-generated by ${path.basename(__filename)}
|
||||
export const e2eProjectDirs = [
|
||||
${allDirs
|
||||
.map((dir) => ` '${path.basename(dir)}'`).join(',\n')}
|
||||
] as const
|
||||
`,
|
||||
)
|
||||
|
||||
return allDirs
|
||||
}
|
||||
|
||||
export async function e2eTestScaffoldWatch () {
|
||||
const fixtureWatcher = chokidar.watch(PROJECT_FIXTURE_DIRECTORY, {
|
||||
cwd: monorepoPaths.pkgServer,
|
||||
// ignoreInitial: true,
|
||||
depth: 0,
|
||||
})
|
||||
|
||||
fixtureWatcher.on('unlinkDir', () => {
|
||||
e2eTestScaffold()
|
||||
})
|
||||
|
||||
fixtureWatcher.on('addDir', () => {
|
||||
e2eTestScaffold()
|
||||
})
|
||||
}
|
||||
@@ -72,6 +72,27 @@ function spawnViteDevServer (
|
||||
* * viteBuildLaunchpad
|
||||
*------------------------------------------------------------------------**/
|
||||
|
||||
export async function symlinkViteProjects () {
|
||||
await Promise.all([
|
||||
spawned('cmd-symlink', 'ln -s ../app/dist dist-app', {
|
||||
cwd: monorepoPaths.pkgLaunchpad,
|
||||
waitForExit: true,
|
||||
}).catch((e) => {}),
|
||||
spawned('cmd-symlink', 'ln -s dist dist-app', {
|
||||
cwd: monorepoPaths.pkgApp,
|
||||
waitForExit: true,
|
||||
}).catch((e) => {}),
|
||||
spawned('cmd-symlink', 'ln -s dist dist-launchpad', {
|
||||
cwd: monorepoPaths.pkgLaunchpad,
|
||||
waitForExit: true,
|
||||
}).catch((e) => {}),
|
||||
spawned('cmd-symlink', 'ln -s ../launchpad/dist dist-launchpad', {
|
||||
cwd: monorepoPaths.pkgApp,
|
||||
waitForExit: true,
|
||||
}).catch((e) => {}),
|
||||
])
|
||||
}
|
||||
|
||||
export function viteBuildApp () {
|
||||
return spawned('vite:build-app', `yarn vite build`, {
|
||||
cwd: monorepoPaths.pkgApp,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { prefixLog, prefixStream } from './prefixStream'
|
||||
import { addChildProcess } from '../tasks/gulpRegistry'
|
||||
|
||||
export type AllSpawnableApps =
|
||||
| `cmd-${string}`
|
||||
| `vite-${string}`
|
||||
| `vite:build-${string}`
|
||||
| `serve:${string}`
|
||||
|
||||
@@ -9045,6 +9045,14 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
|
||||
integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
|
||||
|
||||
"@types/rimraf@^3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-3.0.2.tgz#a63d175b331748e5220ad48c901d7bbf1f44eef8"
|
||||
integrity sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==
|
||||
dependencies:
|
||||
"@types/glob" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/semver@7.3.4":
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.4.tgz#43d7168fec6fa0988bb1a513a697b29296721afb"
|
||||
|
||||
Reference in New Issue
Block a user