mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-12 18:29:53 -05:00
refactor: Add GitDataSource, FileDataSource, toast for errors (#18385)
* refactor: Add GitDataSource, FileDataSource, toast for errors
* Fix
* fix
* Fix: use frontend-shared for common optimizeDeps
* schema/type fixes
* add debug for circle job
* add debug for circle job
* Attempting to debug CI flake
* Fix types
* Attempt to fix
* Revert "Attempt to fix"
This reverts commit 12a8db455a.
* Just use more parallelization?
* See if this fixes it
* remove debug
* Force browser relaunch?
* use internal ENV var
This commit is contained in:
+8
-1
@@ -398,10 +398,16 @@ commands:
|
||||
description: ct or e2e
|
||||
type: enum
|
||||
enum: ['ct', 'e2e']
|
||||
debug:
|
||||
description: debug option
|
||||
type: string
|
||||
default: ''
|
||||
steps:
|
||||
- restore_cached_workspace
|
||||
- run:
|
||||
command: |
|
||||
DEBUG=<<parameters.debug>> \
|
||||
CYPRESS_INTERNAL_FORCE_BROWSER_RELAUNCH='true' \
|
||||
CYPRESS_KONFIG_ENV=production \
|
||||
CYPRESS_RECORD_KEY=$TEST_LAUNCHPAD_RECORD_KEY \
|
||||
yarn workspace @packages/<<parameters.package>> cypress:run:<<parameters.type>> --browser <<parameters.browser>> --record --parallel --group <<parameters.package>>-<<parameters.type>>
|
||||
@@ -1184,12 +1190,13 @@ jobs:
|
||||
|
||||
run-launchpad-component-tests-chrome:
|
||||
<<: *defaults
|
||||
parallelism: 3
|
||||
parallelism: 7
|
||||
steps:
|
||||
- run-new-ui-tests:
|
||||
browser: chrome
|
||||
package: launchpad
|
||||
type: ct
|
||||
# debug: cypress:*,engine:socket
|
||||
|
||||
run-launchpad-integration-tests-chrome:
|
||||
<<: *defaults
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
"@types/detect-port": "^1.3.1",
|
||||
"@types/enzyme-adapter-react-16": "1.0.5",
|
||||
"@types/execa": "0.9.0",
|
||||
"@types/fluent-ffmpeg": "^2.1.18",
|
||||
"@types/fs-extra": "^8.0.1",
|
||||
"@types/getenv": "^1.0.0",
|
||||
"@types/glob": "7.1.1",
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
/**
|
||||
* @type {import('@cypress/vite-dev-server')}
|
||||
*/
|
||||
const { startDevServer } = require('@cypress/vite-dev-server')
|
||||
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
@@ -20,23 +15,4 @@ const { startDevServer } = require('@cypress/vite-dev-server')
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
|
||||
if (config.testingType === 'component') {
|
||||
on('dev-server:start', async (options) => {
|
||||
return startDevServer({
|
||||
options,
|
||||
viteConfig: {
|
||||
// TODO(tim): Figure out why this isn't being picked up
|
||||
optimizeDeps: {
|
||||
include: ['@headlessui/vue'],
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return config // IMPORTANT to return a config
|
||||
}
|
||||
module.exports = require('@packages/frontend-shared/cypress/plugins/index')
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import type { LaunchArgs, OpenProjectLaunchOptions } from '@packages/types'
|
||||
import type { AppApiShape, ProjectApiShape } from './actions'
|
||||
import type { NexusGenAbstractTypeMembers } from '@packages/graphql/src/gen/nxs.gen'
|
||||
import type { AuthApiShape } from './actions/AuthActions'
|
||||
import debugLib from 'debug'
|
||||
import fsExtra from 'fs-extra'
|
||||
import type { AuthApiShape } from './actions/AuthActions'
|
||||
import { CoreDataShape, makeCoreData } from './data/coreDataShape'
|
||||
import { DataActions } from './DataActions'
|
||||
import { AppDataSource } from './sources/AppDataSource'
|
||||
import { ProjectDataSource } from './sources/ProjectDataSource'
|
||||
import {
|
||||
AppDataSource,
|
||||
GitDataSource,
|
||||
FileDataSource,
|
||||
ProjectDataSource,
|
||||
WizardDataSource,
|
||||
BrowserDataSource,
|
||||
UtilDataSource,
|
||||
} from './sources/'
|
||||
import { cached } from './util/cached'
|
||||
import { WizardDataSource } from './sources'
|
||||
import type { AppApiShape, ProjectApiShape } from './actions'
|
||||
import { makeLoaders } from './data/loaders'
|
||||
import { BrowserDataSource } from './sources/BrowserDataSource'
|
||||
import type { NexusGenAbstractTypeMembers } from '@packages/graphql/src/gen/nxs.gen'
|
||||
|
||||
export interface DataContextConfig {
|
||||
launchArgs: LaunchArgs
|
||||
@@ -37,8 +41,6 @@ export class DataContext {
|
||||
this._coreData = config.coreData ?? makeCoreData()
|
||||
}
|
||||
|
||||
loaders = makeLoaders(this)
|
||||
|
||||
get launchArgs () {
|
||||
return this.config.launchArgs
|
||||
}
|
||||
@@ -59,6 +61,21 @@ export class DataContext {
|
||||
return this.coreData.app.browsers
|
||||
}
|
||||
|
||||
@cached
|
||||
get util () {
|
||||
return new UtilDataSource(this)
|
||||
}
|
||||
|
||||
@cached
|
||||
get file () {
|
||||
return new FileDataSource(this)
|
||||
}
|
||||
|
||||
@cached
|
||||
get git () {
|
||||
return new GitDataSource(this)
|
||||
}
|
||||
|
||||
@cached
|
||||
get browser () {
|
||||
return new BrowserDataSource(this)
|
||||
@@ -138,6 +155,10 @@ export class DataContext {
|
||||
}
|
||||
|
||||
dispose () {
|
||||
this.loaders.dispose()
|
||||
this.util.disposeLoaders()
|
||||
}
|
||||
|
||||
get loader () {
|
||||
return this.util.loader
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { DataContext } from '..'
|
||||
|
||||
export class FileActions {
|
||||
constructor (private ctx: DataContext) {}
|
||||
}
|
||||
@@ -55,8 +55,8 @@ export class ProjectActions {
|
||||
return this
|
||||
}
|
||||
|
||||
async findSpecs (projectRoot: string, specType: Maybe<SpecType>): Promise<FoundSpec[]> {
|
||||
const config = await this.ctx.loaders.projectConfig(projectRoot)
|
||||
async findSpecs (projectRoot: string, specType: Maybe<SpecType>) {
|
||||
const config = await this.ctx.project.getConfig(projectRoot)
|
||||
const specs = await this.api.findSpecs({
|
||||
projectRoot,
|
||||
fixturesFolder: config.fixturesFolder ?? false,
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
|
||||
export * from './AppActions'
|
||||
export * from './AuthActions'
|
||||
export * from './FileActions'
|
||||
export * from './ProjectActions'
|
||||
export * from './WizardActions'
|
||||
|
||||
@@ -3,5 +3,3 @@
|
||||
|
||||
export * from './DataEmitter'
|
||||
export * from './coreDataShape'
|
||||
export * from './loaders'
|
||||
export * from './util/'
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import type { FullConfig } from '@packages/types'
|
||||
import DataLoader from 'dataloader'
|
||||
import fs from 'fs-extra'
|
||||
import type { DataContext } from '..'
|
||||
import { GitInfo, getGitInfo } from './util'
|
||||
|
||||
/**
|
||||
* Centralized location to load files. Allows us to consolidate
|
||||
* file watching & cache invalidation in a single location
|
||||
*/
|
||||
export class DataLoaders {
|
||||
constructor (private ctx: DataContext) {}
|
||||
|
||||
private _allLoaders: DataLoader<any, any>[] = []
|
||||
|
||||
file (fileName: string) {
|
||||
return this.fileLoader.load(fileName)
|
||||
}
|
||||
|
||||
maybeFile (fileName: string) {
|
||||
return this.file(fileName).catch(() => null)
|
||||
}
|
||||
|
||||
jsonFile<Result = unknown> (fileName: string) {
|
||||
return this.jsonFileLoader.load(fileName) as Promise<Result>
|
||||
}
|
||||
|
||||
projectConfig (projectRoot: string) {
|
||||
return this.configLoader.load(projectRoot)
|
||||
}
|
||||
|
||||
gitInfo (path: string) {
|
||||
return this.gitLoader.load(path)
|
||||
}
|
||||
|
||||
private gitLoader = this.loader<string, GitInfo>((absolutePaths) => {
|
||||
return getGitInfo(absolutePaths)
|
||||
})
|
||||
|
||||
private configLoader = this.loader<string, FullConfig>((projectRoots) => {
|
||||
return Promise.all(projectRoots.map((root) => this.ctx._apis.projectApi.getConfig(root)))
|
||||
})
|
||||
|
||||
private fileLoader = this.loader<string, string>((files) => {
|
||||
return Promise.all(files.map((f) => fs.readFile(f, 'utf8')))
|
||||
})
|
||||
|
||||
private jsonFileLoader = this.loader<string, unknown>(async (jsonFiles) => {
|
||||
const files = await this.fileLoader.loadMany(jsonFiles)
|
||||
|
||||
return files.map((file) => {
|
||||
if (file instanceof Error) {
|
||||
return file
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(file)
|
||||
} catch (e) {
|
||||
return e
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
private loader<K, V, C = K> (batchLoadFn: DataLoader.BatchLoadFn<K, V>) {
|
||||
const loader = new DataLoader<K, V, C>(batchLoadFn, { cache: false })
|
||||
|
||||
this._allLoaders.push(loader)
|
||||
|
||||
return loader
|
||||
}
|
||||
|
||||
dispose () {
|
||||
for (const loader of this._allLoaders) {
|
||||
loader.clearAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function makeLoaders (ctx: DataContext) {
|
||||
return new DataLoaders(ctx)
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import execa from 'execa'
|
||||
import path from 'path'
|
||||
import os from 'os'
|
||||
import Debug from 'debug'
|
||||
|
||||
export interface GitInfo {
|
||||
author: string | null
|
||||
lastModifiedTimestamp: string | null
|
||||
lastModifiedHumanReadable: string | null
|
||||
}
|
||||
|
||||
const debug = Debug('cypress:data-context:git-info')
|
||||
|
||||
// matches <timestamp> <when> <author>
|
||||
// $ git log -1 --pretty=format:%ci %ar %an <file>
|
||||
// eg '2021-09-14 13:43:19 +1000 2 days ago Lachlan Miller
|
||||
const GIT_LOG_REGEXP = /(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [-+].+?)\s(.+ago)\s(.*)/
|
||||
const GIT_LOG_COMMAND = `git log -1 --pretty="format:%ci %ar %an"`
|
||||
|
||||
const getInfoWindows = async (absolutePaths: readonly string[]) => {
|
||||
const paths = absolutePaths.map((x) => path.resolve(x)).join(',')
|
||||
const cmd = `FOR %x in (${paths}) DO (${GIT_LOG_COMMAND} %x)`
|
||||
const result = await execa(cmd, { shell: true })
|
||||
|
||||
const split = result.stdout
|
||||
.split('\r\n') // windows uses CRLF for carriage returns
|
||||
.filter((str) => !str.includes('git log')) // windows stdout contains [cmd,output]. So we remove the code containing the executed command, `git log`
|
||||
|
||||
// windows returns a leading carriage return, remove it
|
||||
const [, ...stdout] = split
|
||||
|
||||
if (stdout.length !== absolutePaths.length) {
|
||||
debug('stdout', stdout)
|
||||
throw Error(`Expect result array to have same length as input. Input: ${absolutePaths.length} Output: ${stdout.length}`)
|
||||
}
|
||||
|
||||
return stdout
|
||||
}
|
||||
|
||||
const getInfoPosix = async (absolutePaths: readonly string[]) => {
|
||||
debug('getting git info for %o:', absolutePaths)
|
||||
const paths = absolutePaths.map((x) => path.resolve(x)).join(',')
|
||||
|
||||
// for file in {one,two} is valid in bash, but for file {one} is not
|
||||
// no need to use a for loop for a single file
|
||||
const cmd = absolutePaths.length === 1
|
||||
? `${GIT_LOG_COMMAND} ${absolutePaths[0]}`
|
||||
: `for file in {${paths}}; do echo $(${GIT_LOG_COMMAND} $file); done`
|
||||
|
||||
debug('executing command `%s`:', cmd)
|
||||
|
||||
const result = await execa(cmd, { shell: true })
|
||||
const stdout = result.stdout.split('\n')
|
||||
|
||||
if (stdout.length !== absolutePaths.length) {
|
||||
debug('error... stdout:', stdout)
|
||||
throw Error(`Expect result array to have same length as input. Input: ${absolutePaths.length} Output: ${stdout.length}`)
|
||||
}
|
||||
|
||||
debug('stdout for git info', stdout)
|
||||
|
||||
return stdout
|
||||
}
|
||||
|
||||
export const getGitInfo = async (absolutePaths: readonly string[]): Promise<GitInfo[]> => {
|
||||
if (absolutePaths.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
try {
|
||||
const stdout = await (
|
||||
os.platform() === 'win32'
|
||||
? getInfoWindows(absolutePaths)
|
||||
: getInfoPosix(absolutePaths)
|
||||
)
|
||||
|
||||
const output: GitInfo[] = []
|
||||
|
||||
debug('stdout %s', stdout)
|
||||
|
||||
for (let i = 0; i < absolutePaths.length; i++) {
|
||||
const file = absolutePaths[i]
|
||||
const data = stdout[i]
|
||||
const info = data?.match(GIT_LOG_REGEXP)
|
||||
|
||||
if (file && info && info[1] && info[2] && info[3]) {
|
||||
output.push({
|
||||
lastModifiedTimestamp: info[1],
|
||||
lastModifiedHumanReadable: info[2],
|
||||
author: info[3],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
} catch (e) {
|
||||
debug('Error getting git info: %s', e.message)
|
||||
|
||||
// does not have git installed,
|
||||
// file is not under source control
|
||||
// ... etc ...
|
||||
// just return an empty map
|
||||
return []
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/* eslint-disable padding-line-between-statements */
|
||||
// created by autobarrel, do not modify directly
|
||||
|
||||
export * from './gitInfo'
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { DataContext } from '..'
|
||||
|
||||
export class FileDataSource {
|
||||
private watchedFilePaths = new Set<string>()
|
||||
|
||||
constructor (private ctx: DataContext) {}
|
||||
|
||||
readFile (absoluteFilePath: string) {
|
||||
return this.fileLoader.load(absoluteFilePath).catch((e) => {
|
||||
this.fileLoader.clear(absoluteFilePath)
|
||||
throw e
|
||||
})
|
||||
}
|
||||
|
||||
readJsonFile<Result = unknown> (absoluteFilePath: string) {
|
||||
return this.jsonFileLoader.load(absoluteFilePath).catch((e) => {
|
||||
this.jsonFileLoader.clear(e)
|
||||
throw e
|
||||
}) as Promise<Result>
|
||||
}
|
||||
|
||||
private trackFile () {
|
||||
// this.watchedFilePaths.clear()
|
||||
// this.fileLoader.clear()
|
||||
// this.jsonFileLoader.clear()
|
||||
}
|
||||
|
||||
private fileLoader = this.ctx.loader<string, string>((files) => {
|
||||
return this.ctx.util.settleAll(files.map((f) => this.ctx.fs.readFile(f, 'utf8')))
|
||||
})
|
||||
|
||||
private jsonFileLoader = this.ctx.loader<string, unknown>(async (jsonFiles) => {
|
||||
const files = await this.fileLoader.loadMany(jsonFiles)
|
||||
|
||||
return files.map((file) => {
|
||||
if (file instanceof Error) {
|
||||
return file
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(file)
|
||||
} catch (e) {
|
||||
return e
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import execa from 'execa'
|
||||
import path from 'path'
|
||||
import os from 'os'
|
||||
|
||||
import type { DataContext } from '..'
|
||||
|
||||
// matches <timestamp> <when> <author>
|
||||
// $ git log -1 --pretty=format:%ci %ar %an <file>
|
||||
// eg '2021-09-14 13:43:19 +1000 2 days ago Lachlan Miller
|
||||
const GIT_LOG_REGEXP = /(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [-+].+?)\s(.+ago)\s(.*)/
|
||||
const GIT_LOG_COMMAND = `git log -1 --pretty="format:%ci %ar %an"`
|
||||
|
||||
export interface GitInfo {
|
||||
author: string | null
|
||||
lastModifiedTimestamp: string | null
|
||||
lastModifiedHumanReadable: string | null
|
||||
}
|
||||
|
||||
export class GitDataSource {
|
||||
constructor (private ctx: DataContext) {}
|
||||
|
||||
gitInfo (path: string): Promise<GitInfo> {
|
||||
return this.gitInfoLoader.load(path)
|
||||
}
|
||||
|
||||
private gitInfoLoader = this.ctx.loader<string, GitInfo>((paths) => {
|
||||
return this.bulkGitInfo(paths)
|
||||
})
|
||||
|
||||
private async bulkGitInfo (absolutePaths: readonly string[]) {
|
||||
if (absolutePaths.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
try {
|
||||
const stdout = await (
|
||||
os.platform() === 'win32'
|
||||
? this.getInfoWindows(absolutePaths)
|
||||
: this.getInfoPosix(absolutePaths)
|
||||
)
|
||||
|
||||
const output: GitInfo[] = []
|
||||
|
||||
this.ctx.debug('stdout %s', stdout)
|
||||
|
||||
for (let i = 0; i < absolutePaths.length; i++) {
|
||||
const file = absolutePaths[i]
|
||||
const data = stdout[i]
|
||||
const info = data?.match(GIT_LOG_REGEXP)
|
||||
|
||||
if (file && info && info[1] && info[2] && info[3]) {
|
||||
output.push({
|
||||
lastModifiedTimestamp: info[1],
|
||||
lastModifiedHumanReadable: info[2],
|
||||
author: info[3],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
} catch (e) {
|
||||
this.ctx.debug('Error getting git info: %s', (e as Error).message)
|
||||
|
||||
// does not have git installed,
|
||||
// file is not under source control
|
||||
// ... etc ...
|
||||
// just return an empty map
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
private async getInfoPosix (absolutePaths: readonly string[]) {
|
||||
this.ctx.debug('getting git info for %o:', absolutePaths)
|
||||
const paths = absolutePaths.map((x) => path.resolve(x)).join(',')
|
||||
|
||||
// for file in {one,two} is valid in bash, but for file {one} is not
|
||||
// no need to use a for loop for a single file
|
||||
const cmd = absolutePaths.length === 1
|
||||
? `${GIT_LOG_COMMAND} ${absolutePaths[0]}`
|
||||
: `for file in {${paths}}; do echo $(${GIT_LOG_COMMAND} $file); done`
|
||||
|
||||
this.ctx.debug('executing command `%s`:', cmd)
|
||||
|
||||
const result = await execa(cmd, { shell: true })
|
||||
const stdout = result.stdout.split('\n')
|
||||
|
||||
if (stdout.length !== absolutePaths.length) {
|
||||
this.ctx.debug('error... stdout:', stdout)
|
||||
throw Error(`Expect result array to have same length as input. Input: ${absolutePaths.length} Output: ${stdout.length}`)
|
||||
}
|
||||
|
||||
this.ctx.debug('stdout for git info', stdout)
|
||||
|
||||
return stdout
|
||||
}
|
||||
|
||||
private async getInfoWindows (absolutePaths: readonly string[]) {
|
||||
const paths = absolutePaths.map((x) => path.resolve(x)).join(',')
|
||||
const cmd = `FOR %x in (${paths}) DO (${GIT_LOG_COMMAND} %x)`
|
||||
const result = await execa(cmd, { shell: true })
|
||||
|
||||
const split = result.stdout
|
||||
.split('\r\n') // windows uses CRLF for carriage returns
|
||||
.filter((str) => !str.includes('git log')) // windows stdout contains [cmd,output]. So we remove the code containing the executed command, `git log`
|
||||
|
||||
// windows returns a leading carriage return, remove it
|
||||
const [, ...stdout] = split
|
||||
|
||||
if (stdout.length !== absolutePaths.length) {
|
||||
this.ctx.debug('stdout', stdout)
|
||||
throw Error(`Expect result array to have same length as input. Input: ${absolutePaths.length} Output: ${stdout.length}`)
|
||||
}
|
||||
|
||||
return stdout
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { FullConfig } from '@packages/types'
|
||||
import path from 'path'
|
||||
|
||||
import type { DataContext } from '..'
|
||||
@@ -16,12 +17,16 @@ export class ProjectDataSource {
|
||||
}
|
||||
|
||||
getConfig (projectRoot: string) {
|
||||
return this.ctx.loaders.projectConfig(projectRoot)
|
||||
return this.configLoader.load(projectRoot)
|
||||
}
|
||||
|
||||
private configLoader = this.ctx.loader<string, FullConfig>((projectRoots) => {
|
||||
return Promise.all(projectRoots.map((root) => this.ctx._apis.projectApi.getConfig(root)))
|
||||
})
|
||||
|
||||
async isFirstTimeAccessing (projectRoot: string, testingType: 'e2e' | 'component') {
|
||||
try {
|
||||
const config = await this.ctx.loaders.jsonFile<{ e2e?: object, component?: object }>(path.join(projectRoot, 'cypress.json'))
|
||||
const config = await this.ctx.file.readJsonFile<{ e2e?: object, component?: object }>(path.join(projectRoot, 'cypress.json'))
|
||||
const type = testingType === 'e2e' ? 'e2e' : 'component'
|
||||
const overrides = config[type] || {}
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import DataLoader from 'dataloader'
|
||||
import type { DataContext } from '..'
|
||||
|
||||
/**
|
||||
* this.ctx.util....
|
||||
*
|
||||
* Used as a central location for grab-bag utilities used
|
||||
* within the DataContext layer
|
||||
*/
|
||||
export class UtilDataSource {
|
||||
constructor (private ctx: DataContext) {}
|
||||
|
||||
private _allLoaders: DataLoader<any, any>[] = []
|
||||
|
||||
async settleAll<T> (promises: Promise<T>[]) {
|
||||
const vals = await Promise.allSettled(promises)
|
||||
|
||||
return vals.map((v) => v.status === 'fulfilled' ? v.value : this.ensureError(v.reason))
|
||||
}
|
||||
|
||||
ensureError (val: any): Error {
|
||||
return val instanceof Error ? val : new Error(val)
|
||||
}
|
||||
|
||||
loader = <K, V, C = K>(batchLoadFn: DataLoader.BatchLoadFn<K, V>) => {
|
||||
const loader = new DataLoader<K, V, C>(batchLoadFn, { cache: false })
|
||||
|
||||
this._allLoaders.push(loader)
|
||||
|
||||
return loader
|
||||
}
|
||||
|
||||
disposeLoaders () {
|
||||
for (const loader of this._allLoaders) {
|
||||
loader.clearAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,8 @@
|
||||
|
||||
export * from './AppDataSource'
|
||||
export * from './BrowserDataSource'
|
||||
export * from './FileDataSource'
|
||||
export * from './GitDataSource'
|
||||
export * from './ProjectDataSource'
|
||||
export * from './UtilDataSource'
|
||||
export * from './WizardDataSource'
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"script"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"lib": ["es2020"],
|
||||
"importHelpers": true,
|
||||
"strict": true,
|
||||
"allowJs": false,
|
||||
|
||||
@@ -31,7 +31,14 @@ module.exports = (on, config) => {
|
||||
viteConfig: {
|
||||
// TODO(tim): Figure out why this isn't being picked up
|
||||
optimizeDeps: {
|
||||
include: ['@headlessui/vue', 'vue-prism-component', 'vue3-file-selector'],
|
||||
include: [
|
||||
'@headlessui/vue',
|
||||
'vue-prism-component',
|
||||
'vue3-file-selector',
|
||||
'just-my-luck',
|
||||
'combine-properties',
|
||||
'faker',
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
"vue": "3.2.6",
|
||||
"vue-eslint-parser": "7.11.0",
|
||||
"vue-i18n": "^9.2.0-beta.7",
|
||||
"vue-toast-notification": "2.0.1",
|
||||
"vue-tsc": "^0.3.0",
|
||||
"windicss": "3.1.8",
|
||||
"windicss-analysis": "^0.3.4",
|
||||
|
||||
@@ -7,9 +7,21 @@ import {
|
||||
fetchExchange,
|
||||
} from '@urql/core'
|
||||
import { devtoolsExchange } from '@urql/devtools'
|
||||
import VueToast, { ToastPluginApi } from 'vue-toast-notification'
|
||||
import 'vue-toast-notification/dist/theme-sugar.css'
|
||||
|
||||
export { VueToast }
|
||||
|
||||
import { cacheExchange as graphcacheExchange } from '@urql/exchange-graphcache'
|
||||
|
||||
import { GRAPHQL_URL } from '../utils/env'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
$app?: { $toast: ToastPluginApi }
|
||||
}
|
||||
}
|
||||
|
||||
export function makeCacheExchange () {
|
||||
return graphcacheExchange({
|
||||
keys: {
|
||||
@@ -22,10 +34,19 @@ export function makeCacheExchange () {
|
||||
|
||||
export function makeUrqlClient (): Client {
|
||||
const exchanges: Exchange[] = [
|
||||
devtoolsExchange,
|
||||
dedupExchange,
|
||||
errorExchange({
|
||||
onError (error) {
|
||||
const message = `
|
||||
GraphQL Field Path: [${error.graphQLErrors[0].path?.join(', ')}]:<br>
|
||||
${error.message}<br>
|
||||
`
|
||||
|
||||
window.$app?.$toast.error(message, {
|
||||
message,
|
||||
duration: 0,
|
||||
})
|
||||
|
||||
// eslint-disable-next-line
|
||||
console.error(error)
|
||||
},
|
||||
|
||||
@@ -464,6 +464,11 @@ type Spec implements Node {
|
||||
"""
|
||||
absolute: String!
|
||||
|
||||
"""
|
||||
Full name of spec file (e.g. MySpec.test.tsx) without the spec extension
|
||||
"""
|
||||
baseName: String!
|
||||
|
||||
"""The file extension (e.g. tsx, jsx)"""
|
||||
fileExtension: String!
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ export const Spec = objectType({
|
||||
type: GitInfo,
|
||||
description: 'Git information about the spec file',
|
||||
resolve: async (source, args, ctx) => {
|
||||
return ctx.loaders.gitInfo(source.absolute)
|
||||
return ctx.git.gitInfo(source.absolute)
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
"script"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"lib": ["ES2020"],
|
||||
"importHelpers": true,
|
||||
"strict": true,
|
||||
"allowJs": false,
|
||||
"noImplicitAny": true,
|
||||
"noUnusedLocals": false,
|
||||
"resolveJsonModule": true,
|
||||
"experimentalDecorators": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
/**
|
||||
* @type {import('@cypress/vite-dev-server')}
|
||||
*/
|
||||
const { startDevServer } = require('@cypress/vite-dev-server')
|
||||
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
@@ -20,23 +15,4 @@ const { startDevServer } = require('@cypress/vite-dev-server')
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
|
||||
if (config.testingType === 'component') {
|
||||
on('dev-server:start', async (options) => {
|
||||
return startDevServer({
|
||||
options,
|
||||
viteConfig: {
|
||||
// TODO(tim): Figure out why this isn't being picked up
|
||||
optimizeDeps: {
|
||||
include: ['@headlessui/vue', 'vue-prism-component'],
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return config // IMPORTANT to return a config
|
||||
}
|
||||
module.exports = require('@packages/frontend-shared/cypress/plugins/index')
|
||||
|
||||
@@ -3,12 +3,13 @@ import './main.scss'
|
||||
import 'virtual:windi.css'
|
||||
import urql from '@urql/vue'
|
||||
import App from './App.vue'
|
||||
import { makeUrqlClient } from '@packages/frontend-shared/src/graphql/urqlClient'
|
||||
import { makeUrqlClient, VueToast } from '@packages/frontend-shared/src/graphql/urqlClient'
|
||||
import { createI18n } from '@cy/i18n'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(VueToast)
|
||||
app.use(urql, makeUrqlClient())
|
||||
app.use(createI18n())
|
||||
|
||||
app.mount('#app')
|
||||
window.$app = app.mount('#app')
|
||||
|
||||
@@ -1464,7 +1464,8 @@ module.exports = {
|
||||
socketId: options.socketId,
|
||||
webSecurity: options.webSecurity,
|
||||
projectRoot: options.projectRoot,
|
||||
}, options.testingType === 'e2e' || firstSpec),
|
||||
// TODO(tim): investigate the socket disconnect
|
||||
}, process.env.CYPRESS_INTERNAL_FORCE_BROWSER_RELAUNCH || options.testingType === 'e2e' || firstSpec),
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
@@ -15,6 +15,8 @@ import { openFile } from './util/file-opener'
|
||||
import open from './util/open'
|
||||
import type { DestroyableHttpServer } from './util/server_destroy'
|
||||
import * as session from './session'
|
||||
// eslint-disable-next-line no-duplicate-imports
|
||||
import type { Socket } from '@packages/socket'
|
||||
|
||||
type StartListeningCallbacks = {
|
||||
onSocketConnection: (socket: any) => void
|
||||
@@ -184,9 +186,21 @@ export class SocketBase {
|
||||
|
||||
const getFixture = (path, opts) => fixture.get(config.fixturesFolder, path, opts)
|
||||
|
||||
this.io.on('connection', (socket: any) => {
|
||||
this.io.on('connection', (socket: Socket & { inReporterRoom?: boolean, inRunnerRoom?: boolean }) => {
|
||||
debug('socket connected')
|
||||
|
||||
socket.on('disconnecting', (reason) => {
|
||||
debug(`socket-disconnecting ${reason}`)
|
||||
})
|
||||
|
||||
socket.on('disconnect', (reason) => {
|
||||
debug(`socket-disconnect ${reason}`)
|
||||
})
|
||||
|
||||
socket.on('error', (err) => {
|
||||
debug(`socket-error ${err.message}`)
|
||||
})
|
||||
|
||||
// cache the headers so we can access
|
||||
// them at any time
|
||||
const headers = socket.request?.headers ?? {}
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
"./../ts/index.d.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"lib": ["ES2020"],
|
||||
"types": ["mocha", "node"],
|
||||
"importHelpers": true,
|
||||
"resolveJsonModule": true
|
||||
"resolveJsonModule": true,
|
||||
"noUnusedLocals": false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import fs from 'fs'
|
||||
import type http from 'http'
|
||||
import server, { Server as SocketIOBaseServer, ServerOptions } from 'socket.io'
|
||||
import server, { Server as SocketIOBaseServer, ServerOptions, Socket } from 'socket.io'
|
||||
import { client } from './browser'
|
||||
|
||||
export type { Socket }
|
||||
|
||||
const HUNDRED_MEGABYTES = 1e8 // 100000000
|
||||
|
||||
const { version } = require('socket.io-client/package.json')
|
||||
|
||||
@@ -8488,6 +8488,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/filewriter/-/filewriter-0.0.28.tgz#c054e8af4d9dd75db4e63abc76f885168714d4b3"
|
||||
integrity sha1-wFTor02d11205jq8dviFFocU1LM=
|
||||
|
||||
"@types/fluent-ffmpeg@^2.1.18":
|
||||
version "2.1.18"
|
||||
resolved "https://registry.yarnpkg.com/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.18.tgz#72246c2f8c0f0a590aefc1cdbe13736c73f22a81"
|
||||
integrity sha512-LTteOx3RUmnPlKkvhvW9aGOHdJYyEtIiRBVbYVO/zPU65ZYQelbPJ+zBBT+IXup7doMvxVstX7NMoUTWKZOhww==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/fs-extra@^8.0.1":
|
||||
version "8.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.1.tgz#1e49f22d09aa46e19b51c0b013cb63d0d923a068"
|
||||
@@ -41699,6 +41706,11 @@ vue-template-es2015-compiler@^1.9.0, vue-template-es2015-compiler@^1.9.1:
|
||||
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825"
|
||||
integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==
|
||||
|
||||
vue-toast-notification@2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/vue-toast-notification/-/vue-toast-notification-2.0.1.tgz#5fe607c493b5dc9b238bf49bc3bcf80366edd4e4"
|
||||
integrity sha512-8GPJq1J6lsTPTCxSgPhnM8d0v+ivwT+u4R/xmtaDXeYNRkaRxqFOn3ewaVqmm+aCT8Y3/PQQS8dFqkMV7JOf/A==
|
||||
|
||||
vue-tsc@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-0.3.0.tgz#3b3872bf4f1d2e4409b57adbd826032e253db406"
|
||||
|
||||
Reference in New Issue
Block a user