From 61b8c3cd91279c2c7fa4e2193cf90314f057e2a1 Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Wed, 25 Aug 2021 22:16:20 +1000 Subject: [PATCH 01/16] add graphql value flagging whether ct/e2e has been configured --- packages/graphql/schema.graphql | 11 +++ packages/graphql/src/actions/BaseActions.ts | 4 +- packages/graphql/src/entities/LocalProject.ts | 32 +++++++ .../graphql/src/entities/ResolvedConfig.ts | 29 +++++- packages/graphql/src/gen/nxs.gen.ts | 36 ++++--- packages/graphql/test/integration/utils.ts | 31 +++++- .../test/unit/entities/LocalProject.spec.ts | 94 +++++++++++++++++++ 7 files changed, 216 insertions(+), 21 deletions(-) create mode 100644 packages/graphql/test/unit/entities/LocalProject.spec.ts diff --git a/packages/graphql/schema.graphql b/packages/graphql/schema.graphql index c99c6b1792b1..f2290ed50c02 100644 --- a/packages/graphql/schema.graphql +++ b/packages/graphql/schema.graphql @@ -66,6 +66,15 @@ scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/fi """A Cypress project is a container""" type LocalProject { + """ + Whether the user has configured component testing. Based on the existance of a 'component' key in their cypress.json + """ + hasSetupComponentTesting: Boolean! + + """ + Whether the user has configured e2e testing or not, based on the existance of a 'component' key in their cypress.json + """ + hasSetupE2ETesting: Boolean! id: ID! """Used to associate project with Cypress cloud""" @@ -177,9 +186,11 @@ type ResolvedConfig { baseUrl: ResolvedStringOption blockHosts: ResolvedStringOption chromeWebSecurity: ResolvedBooleanOption + component: ResolvedConfig componentFolder: ResolvedStringOption defaultCommandTimeout: ResolvedNumberOption downloadsFolder: ResolvedStringOption + e2e: ResolvedConfig env: ResolvedJsonOption execTimeout: ResolvedNumberOption experimentalFetchPolyfill: ResolvedBooleanOption diff --git a/packages/graphql/src/actions/BaseActions.ts b/packages/graphql/src/actions/BaseActions.ts index 795125168fc5..3cc588c62450 100644 --- a/packages/graphql/src/actions/BaseActions.ts +++ b/packages/graphql/src/actions/BaseActions.ts @@ -2,7 +2,7 @@ import type { BaseContext } from '../context/BaseContext' import type { RunGroup } from '../entities/run' import type { FoundBrowser } from '@packages/launcher' import type { LocalProject } from '../entities' -import type { LaunchArgs, LaunchOpts, OpenProject } from '@packages/server/lib/open_project' +import type { LaunchArgs, LaunchOpts } from '@packages/server/lib/open_project' import type { Cfg, OpenProjectLaunchOptions } from '@packages/server/lib/project-base' import type { BrowserContract } from '../contracts/BrowserContract' @@ -31,7 +31,7 @@ export abstract class BaseActions { abstract getRecordKeys (payload: { projectId: string, authToken: string }): Promise abstract getBrowsers (): Promise - abstract initializeOpenProject (args: LaunchArgs, options: OpenProjectLaunchOptions): Promise + abstract initializeOpenProject (args: LaunchArgs, options: OpenProjectLaunchOptions): Promise abstract launchOpenProject (browser: BrowserContract, spec: Cypress.Cypress['spec'], options: LaunchOpts): Promise abstract resolveOpenProjectConfig (): Cfg | null } diff --git a/packages/graphql/src/entities/LocalProject.ts b/packages/graphql/src/entities/LocalProject.ts index a17af020b54d..1bc165d7da79 100644 --- a/packages/graphql/src/entities/LocalProject.ts +++ b/packages/graphql/src/entities/LocalProject.ts @@ -16,4 +16,36 @@ export class LocalProject extends Project { return new ResolvedConfig(cfg.resolved) } + + @nxs.field.nonNull.boolean({ + description: `Whether the user has configured component testing. Based on the existance of a 'component' key in their cypress.json`, + }) + hasSetupComponentTesting (): NxsResult<'LocalProject', 'hasSetupComponentTesting'> { + // default is {} + // assume if 1 or more key has been configured, CT has been setup + let config: ReturnType + + if (!(config = this.resolvedConfig())) { + return + } + + // default is {} + // assume if 1 or more key has been configured, CT has been setup + return Object.keys(config.resolvedConfig.component?.value).length > 0 ?? false + } + + @nxs.field.nonNull.boolean({ + description: `Whether the user has configured e2e testing or not, based on the existance of a 'component' key in their cypress.json`, + }) + hasSetupE2ETesting (): NxsResult<'LocalProject', 'hasSetupE2ETesting'> { + let config: ReturnType + + if (!(config = this.resolvedConfig())) { + return + } + + // default is {} + // assume if 1 or more key has been configured, E2E has been setup + return Object.keys(config.resolvedConfig.e2e?.value).length > 0 ?? false + } } diff --git a/packages/graphql/src/entities/ResolvedConfig.ts b/packages/graphql/src/entities/ResolvedConfig.ts index c54479a4f306..cd3d01df0364 100644 --- a/packages/graphql/src/entities/ResolvedConfig.ts +++ b/packages/graphql/src/entities/ResolvedConfig.ts @@ -110,8 +110,33 @@ export class ResolvedJsonOption extends ResolvedOptionBase { description: 'Resolve config for a project', }) export class ResolvedConfig { - constructor (private resolvedConfig: ResolvedConfigurationOptions) { - debug('Created new ResolvedConfig %o', resolvedConfig) + resolvedConfig: ResolvedConfigurationOptions + + constructor (_resolvedConfig: ResolvedConfigurationOptions) { + debug('Created new ResolvedConfig %o', _resolvedConfig) + this.resolvedConfig = _resolvedConfig + } + + @nxs.field.type(() => ResolvedConfig) + get component (): NxsResult<'ResolvedConfig', 'e2e'> { + const runnerSpecificConfig = this.resolvedConfig.component?.value ?? null + + if (!runnerSpecificConfig) { + return null + } + + return new ResolvedConfig(runnerSpecificConfig) + } + + @nxs.field.type(() => ResolvedConfig) + get e2e (): NxsResult<'ResolvedConfig', 'e2e'> { + const runnerSpecificConfig = this.resolvedConfig.e2e?.value ?? null + + if (!runnerSpecificConfig) { + return null + } + + return new ResolvedConfig(runnerSpecificConfig) } @nxs.field.type(() => ResolvedStringOption) diff --git a/packages/graphql/src/gen/nxs.gen.ts b/packages/graphql/src/gen/nxs.gen.ts index ce8e7e39192b..9800d6aace75 100644 --- a/packages/graphql/src/gen/nxs.gen.ts +++ b/packages/graphql/src/gen/nxs.gen.ts @@ -6,21 +6,21 @@ import type { BaseContext } from "./../context/BaseContext" -import type { App } from "./../entities/App.js" -import type { DashboardProject } from "./../entities/DashboardProject.js" -import type { NavigationMenu } from "./../entities/NavigationMenu.js" -import type { LocalProject } from "./../entities/LocalProject.js" -import type { ResolvedOptionBase, ResolvedStringOption, ResolvedStringListOption, ResolvedNumberOption, ResolvedBooleanOption, ResolvedJsonOption, ResolvedConfig } from "./../entities/ResolvedConfig.js" -import type { Query } from "./../entities/Query.js" -import type { TestingTypeInfo } from "./../entities/TestingTypeInfo.js" -import type { Viewer } from "./../entities/Viewer.js" -import type { Wizard } from "./../entities/Wizard.js" -import type { WizardBundler } from "./../entities/WizardBundler.js" -import type { WizardFrontendFramework } from "./../entities/WizardFrontendFramework.js" -import type { WizardNpmPackage } from "./../entities/WizardNpmPackage.js" -import type { Browser } from "./../entities/Browser.js" +import type { App } from "./../entities/App" +import type { DashboardProject } from "./../entities/DashboardProject" +import type { NavigationMenu } from "./../entities/NavigationMenu" +import type { LocalProject } from "./../entities/LocalProject" +import type { ResolvedOptionBase, ResolvedStringOption, ResolvedStringListOption, ResolvedNumberOption, ResolvedBooleanOption, ResolvedJsonOption, ResolvedConfig } from "./../entities/ResolvedConfig" +import type { Query } from "./../entities/Query" +import type { TestingTypeInfo } from "./../entities/TestingTypeInfo" +import type { Viewer } from "./../entities/Viewer" +import type { Wizard } from "./../entities/Wizard" +import type { WizardBundler } from "./../entities/WizardBundler" +import type { WizardFrontendFramework } from "./../entities/WizardFrontendFramework" +import type { WizardNpmPackage } from "./../entities/WizardNpmPackage" +import type { Browser } from "./../entities/Browser" import type { RunGroup } from "./../entities/run/Run.js" -import type { NavigationItem } from "./../entities/NavigationItem.js" +import type { NavigationItem } from "./../entities/NavigationItem" import type { RunCommit } from "./../entities/run/RunCommit.js" import type { core } from "nexus" declare global { @@ -142,6 +142,8 @@ export interface NexusGenFieldTypes { title: string; // String! } LocalProject: { // field return type + hasSetupComponentTesting: boolean; // Boolean! + hasSetupE2ETesting: boolean; // Boolean! id: string; // ID! projectId: string | null; // String projectRoot: string; // String! @@ -190,9 +192,11 @@ export interface NexusGenFieldTypes { baseUrl: NexusGenRootTypes['ResolvedStringOption'] | null; // ResolvedStringOption blockHosts: NexusGenRootTypes['ResolvedStringOption'] | null; // ResolvedStringOption chromeWebSecurity: NexusGenRootTypes['ResolvedBooleanOption'] | null; // ResolvedBooleanOption + component: NexusGenRootTypes['ResolvedConfig'] | null; // ResolvedConfig componentFolder: NexusGenRootTypes['ResolvedStringOption'] | null; // ResolvedStringOption defaultCommandTimeout: NexusGenRootTypes['ResolvedNumberOption'] | null; // ResolvedNumberOption downloadsFolder: NexusGenRootTypes['ResolvedStringOption'] | null; // ResolvedStringOption + e2e: NexusGenRootTypes['ResolvedConfig'] | null; // ResolvedConfig env: NexusGenRootTypes['ResolvedJsonOption'] | null; // ResolvedJsonOption execTimeout: NexusGenRootTypes['ResolvedNumberOption'] | null; // ResolvedNumberOption experimentalFetchPolyfill: NexusGenRootTypes['ResolvedBooleanOption'] | null; // ResolvedBooleanOption @@ -347,6 +351,8 @@ export interface NexusGenFieldTypeNames { title: 'String' } LocalProject: { // field return type name + hasSetupComponentTesting: 'Boolean' + hasSetupE2ETesting: 'Boolean' id: 'ID' projectId: 'String' projectRoot: 'String' @@ -395,9 +401,11 @@ export interface NexusGenFieldTypeNames { baseUrl: 'ResolvedStringOption' blockHosts: 'ResolvedStringOption' chromeWebSecurity: 'ResolvedBooleanOption' + component: 'ResolvedConfig' componentFolder: 'ResolvedStringOption' defaultCommandTimeout: 'ResolvedNumberOption' downloadsFolder: 'ResolvedStringOption' + e2e: 'ResolvedConfig' env: 'ResolvedJsonOption' execTimeout: 'ResolvedNumberOption' experimentalFetchPolyfill: 'ResolvedBooleanOption' diff --git a/packages/graphql/test/integration/utils.ts b/packages/graphql/test/integration/utils.ts index e3a8f74ebc4a..a7f65437ed39 100644 --- a/packages/graphql/test/integration/utils.ts +++ b/packages/graphql/test/integration/utils.ts @@ -2,9 +2,14 @@ import axios from 'axios' import type { FoundBrowser } from '@packages/launcher' import { BaseActions, BaseContext, DashboardProject, LocalProject, Viewer, Wizard } from '../../src' import { startGraphQLServer, closeGraphQLServer, setServerContext } from '../../src/server' +import type { LaunchArgs } from '@packages/server/lib/open_project' +import type { Cfg, OpenProjectLaunchOptions } from '@packages/server/lib/project-base' interface TestContextInjectionOptions { wizard?: Wizard + launchArgs?: LaunchArgs + launchOptions?: OpenProjectLaunchOptions + Actions?: typeof TestActions } export class TestActions extends BaseActions { @@ -18,6 +23,15 @@ export class TestActions extends BaseActions { installDependencies () {} createConfigFile () {} + async initializeOpenProject (args: LaunchArgs, options: OpenProjectLaunchOptions) {} + async launchOpenProject () {} + resolveOpenProjectConfig (): Cfg { + return { + projectRoot: '/root/path', + resolved: {}, + } + } + addProject (projectRoot: string) { return new LocalProject(projectRoot, this.ctx) } @@ -67,9 +81,20 @@ export class TestContext extends BaseContext { readonly actions: BaseActions viewer = null - constructor ({ wizard }: TestContextInjectionOptions = {}) { - super() - this.actions = new TestActions(this) + constructor ({ wizard, launchArgs, launchOptions, Actions }: TestContextInjectionOptions = {}) { + super(launchArgs || { + config: {}, + cwd: '/current/working/dir', + _: ['/current/working/dir'], + projectRoot: '/project/root', + invokedFromCli: false, + browser: null, + testingType: 'e2e', + project: '/project/root', + os: 'linux', + }, launchOptions || {}) + + this.actions = Actions ? new Actions(this) : new TestActions(this) if (wizard) { this.wizard = wizard } diff --git a/packages/graphql/test/unit/entities/LocalProject.spec.ts b/packages/graphql/test/unit/entities/LocalProject.spec.ts new file mode 100644 index 000000000000..76f9216f122f --- /dev/null +++ b/packages/graphql/test/unit/entities/LocalProject.spec.ts @@ -0,0 +1,94 @@ +import { expect } from 'chai' +import type { Cfg } from '@packages/server/lib/project-base' +import { LocalProject } from '../../../src' +import { TestActions, TestContext } from '../../integration/utils' + +describe('LocalProject', () => { + describe('#hasSetupE2ETesting', () => { + it('returns false when resolved e2e key has no overrides', () => { + class MyActions extends TestActions { + resolveOpenProjectConfig (): Cfg { + return { + projectRoot: '/project/root', + resolved: { + e2e: { + value: {}, + from: 'default', + }, + }, + } + } + } + + const project = new LocalProject('/foo/bar/', new TestContext({ Actions: MyActions })) + + expect(project.hasSetupE2ETesting()).to.eq(false) + }) + + it('returns true when resolved e2e key has one override', () => { + class MyActions extends TestActions { + resolveOpenProjectConfig (): Cfg { + return { + projectRoot: '/project/root', + resolved: { + e2e: { + value: { + baseUrl: 'http://localhost:3000', + }, + from: 'default', + }, + }, + } + } + } + + const project = new LocalProject('/foo/bar/', new TestContext({ Actions: MyActions })) + + expect(project.hasSetupE2ETesting()).to.eq(true) + }) + }) + + describe('#hasSetupComponentTesting', () => { + it('returns false when resolved component key has no overrides', () => { + class MyActions extends TestActions { + resolveOpenProjectConfig (): Cfg { + return { + projectRoot: '/project/root', + resolved: { + component: { + value: {}, + from: 'default', + }, + }, + } + } + } + + const project = new LocalProject('/foo/bar/', new TestContext({ Actions: MyActions })) + + expect(project.hasSetupComponentTesting()).to.eq(false) + }) + + it('returns true when resolved component key has one override', () => { + class MyActions extends TestActions { + resolveOpenProjectConfig (): Cfg { + return { + projectRoot: '/project/root', + resolved: { + component: { + value: { + baseUrl: 'http://localhost:3000', + }, + from: 'default', + }, + }, + } + } + } + + const project = new LocalProject('/foo/bar/', new TestContext({ Actions: MyActions })) + + expect(project.hasSetupComponentTesting()).to.eq(true) + }) + }) +}) From 9bec51159c54ecbd66d6426041921d916d1e5bdb Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Fri, 27 Aug 2021 17:37:19 +1000 Subject: [PATCH 02/16] init open project immediately --- packages/server/lib/graphql/ServerActions.ts | 4 +++- packages/server/lib/gui/events.ts | 21 +++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/server/lib/graphql/ServerActions.ts b/packages/server/lib/graphql/ServerActions.ts index 55c3d0d3aff5..6068759c4aa3 100644 --- a/packages/server/lib/graphql/ServerActions.ts +++ b/packages/server/lib/graphql/ServerActions.ts @@ -107,7 +107,9 @@ export class ServerActions extends BaseActions { } async initializeOpenProject (args: LaunchArgs, options: OpenProjectLaunchOptions) { - return openProject.create(args.projectRoot, args, options) + await openProject.create(args.projectRoot, args, options) + + return } async launchOpenProject (browser: BrowserContract, spec: any, options: LaunchOpts): Promise { diff --git a/packages/server/lib/gui/events.ts b/packages/server/lib/gui/events.ts index 9266863d3300..39ed2f295a14 100644 --- a/packages/server/lib/gui/events.ts +++ b/packages/server/lib/gui/events.ts @@ -33,6 +33,7 @@ import { graphqlSchema, parse, execute } from '@packages/graphql' import { startGraphQLServer, setServerContext } from '@packages/graphql/src/server' import type { LaunchArgs } from '../open_project' import type { EventEmitter } from 'events' +import type { BrowserContract } from '@packages/graphql/src/contracts/BrowserContract' const nullifyUnserializableValues = (obj) => { // nullify values that cannot be cloned @@ -497,9 +498,27 @@ module.exports = { // TODO: Figure out how we want to cleanup & juggle the config, so it's not jammed // into the projects + startGraphQLServer() + const serverContext = setServerContext(new ServerContext(options, {})) - startGraphQLServer() + await serverContext.app.cacheBrowsers() + await serverContext.actions.initializeOpenProject({ + ...options, + config: { + browsers: serverContext.app.browserCache!.map((x): BrowserContract => { + return { + name: x.name, + family: x.family, + majorVersion: x.majorVersion, + channel: x.channel, + displayName: x.displayName, + path: x.path, + version: x.version, + } + }), + }, + }, {}) if (options.projectRoot) { serverContext.actions.addProject(options.projectRoot) From bc3366db73c83202a8d8fcf10eb19e64066198b7 Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Fri, 27 Aug 2021 19:06:26 +1000 Subject: [PATCH 03/16] create testing card component --- packages/launchpad/src/setup/Auth.vue | 6 +- packages/launchpad/src/setup/TestingType.vue | 1 - .../launchpad/src/setup/TestingTypeCard.vue | 58 +++++++++++++++++++ packages/launchpad/src/setup/Wizard.vue | 25 ++++++-- 4 files changed, 79 insertions(+), 11 deletions(-) create mode 100644 packages/launchpad/src/setup/TestingTypeCard.vue diff --git a/packages/launchpad/src/setup/Auth.vue b/packages/launchpad/src/setup/Auth.vue index e0c23b72f9b0..39db5658135f 100644 --- a/packages/launchpad/src/setup/Auth.vue +++ b/packages/launchpad/src/setup/Auth.vue @@ -14,7 +14,7 @@ \ No newline at end of file diff --git a/packages/launchpad/src/setup/TestingType.vue b/packages/launchpad/src/setup/TestingType.vue index 990904ef6cf9..3a9982ac22bc 100644 --- a/packages/launchpad/src/setup/TestingType.vue +++ b/packages/launchpad/src/setup/TestingType.vue @@ -20,7 +20,6 @@ import { defineComponent, PropType } from "vue"; import { gql } from '@urql/core' import { useMutation } from '@urql/vue' -import { TestingTypeIcons } from "../utils/icons"; import { TestingTypeSelectDocument, TestingTypeFragment, TestingTypeEnum } from '../generated/graphql' gql` diff --git a/packages/launchpad/src/setup/TestingTypeCard.vue b/packages/launchpad/src/setup/TestingTypeCard.vue new file mode 100644 index 000000000000..0e5f4a8284e7 --- /dev/null +++ b/packages/launchpad/src/setup/TestingTypeCard.vue @@ -0,0 +1,58 @@ + + + diff --git a/packages/launchpad/src/setup/Wizard.vue b/packages/launchpad/src/setup/Wizard.vue index de7f986cd9e3..cba23445a7e1 100644 --- a/packages/launchpad/src/setup/Wizard.vue +++ b/packages/launchpad/src/setup/Wizard.vue @@ -6,7 +6,15 @@

{{ wizard.title }}

- +