From 246501c65eacc3721f6c81dcb5b4921c893f0ace Mon Sep 17 00:00:00 2001 From: Bill Glesias Date: Thu, 16 Oct 2025 18:01:02 -0400 Subject: [PATCH 1/3] chore: bundle the browser and node entry points for packages/socket (#32726) --- guides/esm-migration.md | 8 ++++-- .../cypress/component/support/ctSupport.ts | 2 +- packages/app/index.d.ts | 2 +- packages/app/src/runner/event-manager.ts | 2 +- packages/app/src/runner/index.ts | 2 +- .../src/actions/ServersActions.ts | 3 +-- .../data-context/src/data/coreDataShape.ts | 3 +-- packages/extension/app/v2/client.ts | 2 +- .../test/integration/v2/background_spec.js | 4 +-- .../frontend-shared/src/graphql/urqlClient.ts | 4 +-- .../src/graphql/urqlExchangePubsub.ts | 2 +- .../src/graphql/urqlFetchSocketAdapter.ts | 2 +- packages/network/test/unit/agent.spec.ts | 2 +- packages/server/lib/browsers/chrome.ts | 2 +- packages/server/lib/browsers/electron.ts | 2 +- packages/server/lib/browsers/index.ts | 2 +- packages/server/lib/browsers/types.ts | 2 +- packages/server/lib/socket-base.ts | 2 +- .../test/integration/websockets_spec.js | 4 ++- packages/server/test/unit/socket_spec.js | 18 ++++++++++++- packages/socket/.eslintignore | 4 +++ packages/socket/.gitignore | 3 +++ packages/socket/README.md | 19 +++----------- packages/socket/index.js | 5 ---- packages/socket/lib/{ => client}/browser.ts | 10 +++----- .../socket/lib/{ => client}/cdp-browser.ts | 7 +++--- packages/socket/lib/client/index.ts | 7 ++++++ packages/socket/lib/{ => node}/cdp-socket.ts | 3 ++- packages/socket/lib/node/index.ts | 5 ++++ packages/socket/lib/{ => node}/socket.ts | 15 +++-------- packages/socket/lib/{ => node}/types.ts | 0 packages/socket/package.json | 23 +++++++++++------ packages/socket/test/socket.spec.ts | 9 ++----- packages/socket/tsconfig.browser.json | 18 +++++++++++++ packages/socket/tsconfig.cjs.json | 18 +++++++++++++ packages/socket/tsconfig.esm.json | 19 ++++++++++++++ packages/socket/tsconfig.json | 21 ++++++++-------- .../projects/non-proxied/cypress.config.js | 2 +- .../cache/darwin/snapshot-meta.json | 25 ++++++++----------- .../cache/linux/snapshot-meta.json | 25 ++++++++----------- .../cache/win32/snapshot-meta.json | 25 ++++++++----------- 41 files changed, 196 insertions(+), 137 deletions(-) create mode 100644 packages/socket/.gitignore delete mode 100644 packages/socket/index.js rename packages/socket/lib/{ => client}/browser.ts (88%) rename packages/socket/lib/{ => client}/cdp-browser.ts (95%) create mode 100644 packages/socket/lib/client/index.ts rename packages/socket/lib/{ => node}/cdp-socket.ts (98%) create mode 100644 packages/socket/lib/node/index.ts rename packages/socket/lib/{ => node}/socket.ts (75%) rename packages/socket/lib/{ => node}/types.ts (100%) create mode 100644 packages/socket/tsconfig.browser.json create mode 100644 packages/socket/tsconfig.cjs.json create mode 100644 packages/socket/tsconfig.esm.json diff --git a/guides/esm-migration.md b/guides/esm-migration.md index 7dd4413919..c4b12c285d 100644 --- a/guides/esm-migration.md +++ b/guides/esm-migration.md @@ -12,7 +12,11 @@ #### Notes -When migrating some of these projects away from the `ts-node` entry [see `@packages/scaffold-config` example](https://github.com/cypress-io/cypress/blob/v15.2.0/packages/scaffold-config/index.js), it is somewhat difficult to make separate browser/node entries as the v8-snapshot [tsconfig.json](https://github.com/cypress-io/cypress/blob/v15.2.0/tooling/v8-snapshot/tsconfig.json) is using an older style of module resolution where the `exports` key inside a package's `package.json` is not well supported. Because of this, we need to find ways to bundle code that is needed internally in the browser vs in node without them being a part of the same bundle. This is a temporary work around until we are able to get every package being able to build as an ES Module, which as that point we can re assess how the Cypress binary is being built as well as v8-snapshots, and will allow us to reconfigure this packages to export content in a more proper fashion. +When migrating some of these projects away from the `ts-node` entry [see `@packages/scaffold-config` example](https://github.com/cypress-io/cypress/blob/v15.2.0/packages/scaffold-config/index.js), it is somewhat difficult to make separate browser/node entries as the v8-snapshot [tsconfig.json](https://github.com/cypress-io/cypress/blob/v15.2.0/tooling/v8-snapshot/tsconfig.json) is using an older style of module resolution where the `exports` key inside a package's `package.json` is not well supported. Because of this, we need to find ways to bundle code that is needed internally in the browser vs in node without them being a part of the same bundle. This is a temporary work around until we are able to get every package being able to build as an ES Module, which as that point we can re assess how the Cypress binary is being built as well as v8-snapshots, and will allow us to reconfigure this packages to export content in a more proper fashion. We are currently doing something similar in the following packages: + +* `@packages/scaffold-config` +* `@packages/socket` +* `@packages/telemetry` #### Status @@ -63,7 +67,7 @@ When migrating some of these projects away from the `ts-node` entry [see `@packa - [x] packages/runner ✅ **COMPLETED** - [x] packages/scaffold-config ✅ **COMPLETED** - [ ] packages/server **PARTIAL** - many source/test files in JS. highest priority -- [ ] packages/socket **PARTIAL** - entry point is JS. Tests are JS +- [x] packages/socket ✅ **COMPLETED** - [x] packages/stderr-filtering ✅ **COMPLETED** - [x] packages/telemetry ✅ **COMPLETED** - [ ] packages/ts **PARTIAL** - ultimate goal is removal and likely not worth the effort to convert diff --git a/packages/app/cypress/component/support/ctSupport.ts b/packages/app/cypress/component/support/ctSupport.ts index 737d9a58e0..a93f07b717 100644 --- a/packages/app/cypress/component/support/ctSupport.ts +++ b/packages/app/cypress/component/support/ctSupport.ts @@ -1,6 +1,6 @@ import { AutIframe } from '../../../src/runner/aut-iframe' import { EventManager } from '../../../src/runner/event-manager' -import type { Socket } from '@packages/socket/lib/browser' +import type { Socket } from '@packages/socket/browser/client' export const StubWebsocket = new Proxy(Object.create(null), { get: (obj, prop) => { diff --git a/packages/app/index.d.ts b/packages/app/index.d.ts index 09b67d9426..5fb309f7dd 100644 --- a/packages/app/index.d.ts +++ b/packages/app/index.d.ts @@ -1,6 +1,6 @@ /// -import type { SocketShape } from '@packages/socket/lib/types' +import type { SocketShape } from '@packages/socket/browser/client' import type MobX from 'mobx' import type { EventManager } from './src/runner/event-manager' diff --git a/packages/app/src/runner/event-manager.ts b/packages/app/src/runner/event-manager.ts index f1e8f1e11d..1a484af968 100644 --- a/packages/app/src/runner/event-manager.ts +++ b/packages/app/src/runner/event-manager.ts @@ -7,7 +7,7 @@ import type { LocalBusEmitsMap, LocalBusEventMap, DriverToLocalBus, SocketToDriv import type { RunState, CachedTestState, AutomationElementId, FileDetails, ReporterStartInfo, ReporterRunState } from '@packages/types' import { logger } from './logger' -import type { SocketShape } from '@packages/socket/lib/types' +import type { SocketShape } from '@packages/socket/browser/client' import { automation, useRunnerUiStore, useSpecStore } from '../store' import { useScreenshotStore } from '../store/screenshot-store' import { useStudioStore } from '../store/studio-store' diff --git a/packages/app/src/runner/index.ts b/packages/app/src/runner/index.ts index 9ce7f90369..0692c278b7 100644 --- a/packages/app/src/runner/index.ts +++ b/packages/app/src/runner/index.ts @@ -22,7 +22,7 @@ import { getRunnerElement, empty } from './utils' import { IframeModel } from './iframe-model' import { AutIframe } from './aut-iframe' import { EventManager } from './event-manager' -import { createWebsocket as createWebsocketIo } from '@packages/socket/lib/browser' +import { createWebsocket as createWebsocketIo } from '@packages/socket/browser/client' import type { AutomationElementId } from '@packages/types' import { useSnapshotStore } from './snapshot-store' import { useStudioStore } from '../store/studio-store' diff --git a/packages/data-context/src/actions/ServersActions.ts b/packages/data-context/src/actions/ServersActions.ts index ea13acdee9..d7e8309c8c 100644 --- a/packages/data-context/src/actions/ServersActions.ts +++ b/packages/data-context/src/actions/ServersActions.ts @@ -2,9 +2,8 @@ import util from 'util' import type { AddressInfo } from 'net' import type { Server } from 'http' -import type { SocketIONamespace, SocketIOServer } from '@packages/socket' +import type { SocketIONamespace, SocketIOServer, CDPSocketServer } from '@packages/socket' import type { DataContext } from '..' -import type { CDPSocketServer } from '@packages/socket/lib/cdp-socket' export class ServersActions { constructor (private ctx: DataContext) {} diff --git a/packages/data-context/src/data/coreDataShape.ts b/packages/data-context/src/data/coreDataShape.ts index 09c3e02ed1..cbe28313ce 100644 --- a/packages/data-context/src/data/coreDataShape.ts +++ b/packages/data-context/src/data/coreDataShape.ts @@ -4,12 +4,11 @@ import type { NexusGenObjects } from '../gen/nxs.gen' // tslint:disable-next-line no-implicit-dependencies - electron dep needs to be defined import type { App, BrowserWindow } from 'electron' import type { ChildProcess } from 'child_process' -import type { SocketIONamespace, SocketIOServer } from '@packages/socket' +import type { SocketIONamespace, SocketIOServer, CDPSocketServer } from '@packages/socket' import type { Server } from 'http' import type { ErrorWrapperSource } from '@packages/errors' import type { EventCollectorSource, GitDataSource } from '../sources' import { machineId as getMachineId } from 'node-machine-id' -import type { CDPSocketServer } from '@packages/socket/lib/cdp-socket' export type Maybe = T | null | undefined diff --git a/packages/extension/app/v2/client.ts b/packages/extension/app/v2/client.ts index f55c0ab209..fadcab51ce 100644 --- a/packages/extension/app/v2/client.ts +++ b/packages/extension/app/v2/client.ts @@ -1,4 +1,4 @@ -import { client } from '@packages/socket/lib/browser' +import { client } from '@packages/socket/browser/client' export const connect = (host, path, extraOpts = {}) => { return client(host, { diff --git a/packages/extension/test/integration/v2/background_spec.js b/packages/extension/test/integration/v2/background_spec.js index eef57bda37..6eb2ce06b9 100644 --- a/packages/extension/test/integration/v2/background_spec.js +++ b/packages/extension/test/integration/v2/background_spec.js @@ -2,7 +2,7 @@ require('../../spec_helper') const _ = require('lodash') const http = require('http') -const socket = require('@packages/socket') +const { SocketIOServer } = require('@packages/socket') const mockRequire = require('mock-require') const client = require('../../../app/v2/client') @@ -38,7 +38,7 @@ describe('app/background', () => { global.window = {} this.httpSrv = http.createServer() - this.server = socket.server(this.httpSrv, { path: '/__socket' }) + this.server = new SocketIOServer(this.httpSrv, { path: '/__socket' }) const ws = { on: sinon.stub(), diff --git a/packages/frontend-shared/src/graphql/urqlClient.ts b/packages/frontend-shared/src/graphql/urqlClient.ts index 22163a205a..2999df6b4c 100644 --- a/packages/frontend-shared/src/graphql/urqlClient.ts +++ b/packages/frontend-shared/src/graphql/urqlClient.ts @@ -10,8 +10,8 @@ import { } from '@urql/core' import { devtoolsExchange } from '@urql/devtools' import { useToast } from 'vue-toastification' -import type { SocketShape } from '@packages/socket/lib/types' -import { client } from '@packages/socket/lib/browser' +import type { SocketShape } from '@packages/socket/browser/client' +import { client } from '@packages/socket/browser/client' import { createClient as createWsClient } from 'graphql-ws' import { cacheExchange as graphcacheExchange } from '@urql/exchange-graphcache' diff --git a/packages/frontend-shared/src/graphql/urqlExchangePubsub.ts b/packages/frontend-shared/src/graphql/urqlExchangePubsub.ts index 2cadb7bd5a..7d5dd3798f 100644 --- a/packages/frontend-shared/src/graphql/urqlExchangePubsub.ts +++ b/packages/frontend-shared/src/graphql/urqlExchangePubsub.ts @@ -1,6 +1,6 @@ import { pipe, tap } from 'wonka' import type { Exchange, Operation, OperationResult } from '@urql/core' -import type { SocketShape } from '@packages/socket/lib/types' +import type { SocketShape } from '@packages/socket/browser/client' import type { DefinitionNode, DocumentNode, OperationDefinitionNode } from 'graphql' export const pubSubExchange = (io: SocketShape): Exchange => { diff --git a/packages/frontend-shared/src/graphql/urqlFetchSocketAdapter.ts b/packages/frontend-shared/src/graphql/urqlFetchSocketAdapter.ts index c592434196..e24414ab59 100644 --- a/packages/frontend-shared/src/graphql/urqlFetchSocketAdapter.ts +++ b/packages/frontend-shared/src/graphql/urqlFetchSocketAdapter.ts @@ -1,5 +1,5 @@ import _ from 'lodash' -import type { SocketShape } from '@packages/socket/lib/types' +import type { SocketShape } from '@packages/socket/browser/client' import type { ClientOptions } from '@urql/core' export const urqlFetchSocketAdapter = (io: SocketShape): ClientOptions['fetch'] => { diff --git a/packages/network/test/unit/agent.spec.ts b/packages/network/test/unit/agent.spec.ts index 26318a6b21..a3627975f3 100644 --- a/packages/network/test/unit/agent.spec.ts +++ b/packages/network/test/unit/agent.spec.ts @@ -9,7 +9,7 @@ import url from 'url' import DebuggingProxy from '@cypress/debugging-proxy' import Request from '@cypress/request-promise' -import * as socketIo from '@packages/socket/lib/browser' +import * as socketIo from '@packages/socket/browser/client' import { buildConnectReqHead, createProxySock, diff --git a/packages/server/lib/browsers/chrome.ts b/packages/server/lib/browsers/chrome.ts index 03bd17bd7e..eaeb5069b5 100644 --- a/packages/server/lib/browsers/chrome.ts +++ b/packages/server/lib/browsers/chrome.ts @@ -21,7 +21,7 @@ import type { Automation } from '../automation' import memory from './memory' import type { BrowserLaunchOpts, BrowserNewTabOpts, ProtocolManagerShape, CyPromptManagerShape, RunModeVideoApi } from '@packages/types' -import type { CDPSocketServer } from '@packages/socket/lib/cdp-socket' +import type { CDPSocketServer } from '@packages/socket' import { DEFAULT_CHROME_FLAGS } from '../util/chromium_flags' const debug = debugModule('cypress:server:browsers:chrome') diff --git a/packages/server/lib/browsers/electron.ts b/packages/server/lib/browsers/electron.ts index fb75f9d759..2a3e252762 100644 --- a/packages/server/lib/browsers/electron.ts +++ b/packages/server/lib/browsers/electron.ts @@ -13,7 +13,7 @@ import type { Browser, BrowserInstance, GracefulShutdownOptions } from './types' import type { BrowserWindow } from 'electron' import type { Automation } from '../automation' import type { BrowserLaunchOpts, Preferences, ProtocolManagerShape, CyPromptManagerShape, RunModeVideoApi } from '@packages/types' -import type { CDPSocketServer } from '@packages/socket/lib/cdp-socket' +import type { CDPSocketServer } from '@packages/socket' import memory from './memory' import { BrowserCriClient } from './browser-cri-client' import { getRemoteDebuggingPort } from '../util/electron-app' diff --git a/packages/server/lib/browsers/index.ts b/packages/server/lib/browsers/index.ts index a2a2ba999c..14c6391fae 100644 --- a/packages/server/lib/browsers/index.ts +++ b/packages/server/lib/browsers/index.ts @@ -10,7 +10,7 @@ import { BROWSER_FAMILY, BrowserLaunchOpts, BrowserNewTabOpts, FoundBrowser, Pro import type { Browser, BrowserInstance, BrowserLauncher } from './types' import type { Automation } from '../automation' import type { DataContext } from '@packages/data-context' -import type { CDPSocketServer } from '@packages/socket/lib/cdp-socket' +import type { CDPSocketServer } from '@packages/socket' const debug = Debug('cypress:server:browsers') const isBrowserFamily = (browser: string) => BROWSER_FAMILY.includes(browser) diff --git a/packages/server/lib/browsers/types.ts b/packages/server/lib/browsers/types.ts index 59668bd419..55a3ac0b6f 100644 --- a/packages/server/lib/browsers/types.ts +++ b/packages/server/lib/browsers/types.ts @@ -1,7 +1,7 @@ import type { FoundBrowser, BrowserLaunchOpts, BrowserNewTabOpts, ProtocolManagerShape, CyPromptManagerShape } from '@packages/types' import type { EventEmitter } from 'events' import type { Automation } from '../automation' -import type { CDPSocketServer } from '@packages/socket/lib/cdp-socket' +import type { CDPSocketServer } from '@packages/socket' export type Browser = FoundBrowser & { majorVersion: number | string diff --git a/packages/server/lib/socket-base.ts b/packages/server/lib/socket-base.ts index 35125d95d8..052eab7515 100644 --- a/packages/server/lib/socket-base.ts +++ b/packages/server/lib/socket-base.ts @@ -6,7 +6,7 @@ import { getCtx } from '@packages/data-context' import { handleGraphQLSocketRequest } from '@packages/data-context/graphql/makeGraphQLServer' import { onNetStubbingEvent } from '@packages/net-stubbing' import * as socketIo from '@packages/socket' -import { CDPSocketServer } from '@packages/socket/lib/cdp-socket' +import { CDPSocketServer } from '@packages/socket' import * as errors from './errors' import fixture from './fixture' diff --git a/packages/server/test/integration/websockets_spec.js b/packages/server/test/integration/websockets_spec.js index a786e1dbbb..3c2bbabd26 100644 --- a/packages/server/test/integration/websockets_spec.js +++ b/packages/server/test/integration/websockets_spec.js @@ -5,7 +5,9 @@ const ws = require('ws') const httpsProxyAgent = require('https-proxy-agent') const evilDns = require('evil-dns') const Promise = require('bluebird') -const socketIo = require(`@packages/socket/lib/browser`) +// NOTE: we need to import the client from the lib directory because the browser/client directory is compiled to ESM. +// we are unable to import ESM into a CommonJS test context, even if we await import() the module. +const socketIo = require('@packages/socket/lib/client') const httpsServer = require(`@packages/https-proxy/test/helpers/https_server`) const config = require(`../../lib/config`) const { ServerBase } = require(`../../lib/server-base`) diff --git a/packages/server/test/unit/socket_spec.js b/packages/server/test/unit/socket_spec.js index 05923b23e0..ad4173f639 100644 --- a/packages/server/test/unit/socket_spec.js +++ b/packages/server/test/unit/socket_spec.js @@ -3,7 +3,10 @@ require('../spec_helper') const _ = require('lodash') const path = require('path') const httpsAgent = require('https-proxy-agent') -const socketIo = require('@packages/socket/lib/browser') +// NOTE: we need to import the client from the lib directory because the browser/client directory is compiled to ESM. +// we are unable to import ESM into a CommonJS test context, even if we await import() the module. +const socketIo = require('@packages/socket/lib/client') + const Fixtures = require('@tooling/system-tests') const errors = require('../../lib/errors') @@ -28,6 +31,19 @@ describe('lib/socket', () => { path: 'path-to-browser-one', } + // needed to run these tests locally + // sinon.stub(ctx.browser, 'machineBrowsers').resolves([ + // { + // channel: 'stable', + // displayName: 'Electron', + // family: 'chromium', + // majorVersion: '123', + // name: 'electron', + // path: 'path-to-browser-one', + // version: '123.45.67', + // }, + // ]) + // Don't bother initializing the child process, etc for this sinon.stub(ctx.actions.project, 'initializeActiveProject') sinon.stub(preprocessor.emitter, 'on') diff --git a/packages/socket/.eslintignore b/packages/socket/.eslintignore index d5857ea71f..2733312442 100644 --- a/packages/socket/.eslintignore +++ b/packages/socket/.eslintignore @@ -1 +1,5 @@ **/tsconfig.json + +cjs/ +esm/ +browser/ \ No newline at end of file diff --git a/packages/socket/.gitignore b/packages/socket/.gitignore new file mode 100644 index 0000000000..0049f1f5cb --- /dev/null +++ b/packages/socket/.gitignore @@ -0,0 +1,3 @@ +cjs/ +esm/ +browser/ \ No newline at end of file diff --git a/packages/socket/README.md b/packages/socket/README.md index 037fbdd846..6f5407853d 100644 --- a/packages/socket/README.md +++ b/packages/socket/README.md @@ -1,32 +1,19 @@ # Socket -This is a shared lib for holding both the `socket.io` server and client. +This is a shared lib for holding both the `socket.io` Cypress implements of the server and client. ## Using ```javascript const socket = require("@packages/socket") -// returns -{ - server: require("socket.io"), - getPathToClientSource: function () { - // returns path to the client 'socket.io.js' file - // for use in the browser - } -} -``` - -```javascript -const socket = require("@packages/socket") - // server usage const srv = require("http").createServer() -const io = socket.server(srv) +const io = new SocketIOServer(srv) io.on("connection", function(){}) // client usage -const { client } = require("@packages/socket/lib/client") +const { client } = require("@packages/socket/browser/client") const client = socket.client("http://localhost:2020") client.on("connect", function(){}) client.on("event", function(){}) diff --git a/packages/socket/index.js b/packages/socket/index.js deleted file mode 100644 index bd97e75545..0000000000 --- a/packages/socket/index.js +++ /dev/null @@ -1,5 +0,0 @@ -if (process.env.CYPRESS_INTERNAL_ENV !== 'production') { - require('@packages/ts/register') -} - -module.exports = require('./lib/socket') diff --git a/packages/socket/lib/browser.ts b/packages/socket/lib/client/browser.ts similarity index 88% rename from packages/socket/lib/browser.ts rename to packages/socket/lib/client/browser.ts index c097f3eca9..faf972a253 100644 --- a/packages/socket/lib/browser.ts +++ b/packages/socket/lib/client/browser.ts @@ -1,8 +1,6 @@ import io, { ManagerOptions, SocketOptions } from 'socket.io-client' import { CDPBrowserSocket } from './cdp-browser' -import type { SocketShape } from './types' - -export type { Socket } from 'socket.io-client' +import type { SocketShape } from './cdp-browser' declare global { interface Window { @@ -26,8 +24,7 @@ export function client (uri: string, opts?: Partial -import Emitter from 'component-emitter' import { v4 as uuidv4 } from 'uuid' -import { decode, encode } from './utils' -import type { SocketShape } from './types' +import { decode, encode } from '../utils' +import Emitter from 'component-emitter' + +export type SocketShape = Emitter type CDPSocketNamespaceKey = `cypressSocket-${string}` type CDPSendToServerNamespaceKey = `cypressSendToServer-${string}` diff --git a/packages/socket/lib/client/index.ts b/packages/socket/lib/client/index.ts new file mode 100644 index 0000000000..3361a31404 --- /dev/null +++ b/packages/socket/lib/client/index.ts @@ -0,0 +1,7 @@ +export { client, createWebsocket } from './browser' + +export { CDPBrowserSocket } from './cdp-browser' + +export type { Socket } from 'socket.io-client' + +export type { SocketShape } from './cdp-browser' diff --git a/packages/socket/lib/cdp-socket.ts b/packages/socket/lib/node/cdp-socket.ts similarity index 98% rename from packages/socket/lib/cdp-socket.ts rename to packages/socket/lib/node/cdp-socket.ts index 15acf3347a..102c28ecbf 100644 --- a/packages/socket/lib/cdp-socket.ts +++ b/packages/socket/lib/node/cdp-socket.ts @@ -2,7 +2,7 @@ import type { CDPClient } from '@packages/types/src/protocol' import type Protocol from 'devtools-protocol/types/protocol.d' import { EventEmitter } from 'stream' import { randomUUID } from 'crypto' -import { decode, encode } from './utils' +import { decode, encode } from '../utils' import Debug from 'debug' const debugVerbose = Debug('cypress-verbose:server:socket:cdp-socket') @@ -36,6 +36,7 @@ export class CDPSocketServer extends EventEmitter { // @ts-expect-error TODO: fix emit type emit = async (event: string, ...args: any[]) => { + // tslint:disable-next-line:no-floating-promises this._cdpSocket?.emit(event, ...args) return true diff --git a/packages/socket/lib/node/index.ts b/packages/socket/lib/node/index.ts new file mode 100644 index 0000000000..ac31e8272f --- /dev/null +++ b/packages/socket/lib/node/index.ts @@ -0,0 +1,5 @@ +export { SocketIOServer, getPathToClientSource, getClientVersion } from './socket' + +export type { ServerOptions, Socket, Namespace as SocketIONamespace } from 'socket.io' + +export { CDPSocketServer } from './cdp-socket' diff --git a/packages/socket/lib/socket.ts b/packages/socket/lib/node/socket.ts similarity index 75% rename from packages/socket/lib/socket.ts rename to packages/socket/lib/node/socket.ts index 0aed1ba3b9..95f511cbb0 100644 --- a/packages/socket/lib/socket.ts +++ b/packages/socket/lib/node/socket.ts @@ -1,18 +1,15 @@ import buffer from 'buffer' import type http from 'http' -import server, { Server as SocketIOBaseServer, ServerOptions, Socket, Namespace } from 'socket.io' - -export type { Socket, Namespace as SocketIONamespace } +import { Server as SocketIOBaseServer, ServerOptions } from 'socket.io' +// TODO: this will need to be updated to use an ESM version of the package const { version } = require('socket.io-client/package.json') const clientSource = require.resolve('socket.io-client/dist/socket.io.js') -export { ServerOptions } - // socket.io types are incorrect type PatchedServerOptions = ServerOptions & { cookie: { name: string | boolean } } -class SocketIOServer extends SocketIOBaseServer { +export class SocketIOServer extends SocketIOBaseServer { constructor (srv: http.Server, opts?: Partial) { opts = opts ?? {} @@ -27,12 +24,6 @@ class SocketIOServer extends SocketIOBaseServer { } } -export { - server, - SocketIOServer, -} - -// TODO: I don't know that this is used anywhere? export const getPathToClientSource = () => { return clientSource } diff --git a/packages/socket/lib/types.ts b/packages/socket/lib/node/types.ts similarity index 100% rename from packages/socket/lib/types.ts rename to packages/socket/lib/node/types.ts diff --git a/packages/socket/package.json b/packages/socket/package.json index d004802155..24a443e120 100644 --- a/packages/socket/package.json +++ b/packages/socket/package.json @@ -2,18 +2,23 @@ "name": "@packages/socket", "version": "0.0.0-development", "private": true, - "browser": "lib/browser.ts", + "main": "cjs/node/index.js", + "browser": "browser/client/index.js", "scripts": { - "build-prod": "tsc || echo 'built, with type errors' && rm lib/browser.js", - "check-ts": "tsc --noEmit && yarn -s tslint", - "clean": "rimraf --glob 'lib/*.js'", + "build": "yarn build:browser && yarn build:node && yarn build:node:esm", + "build-prod": "yarn build", + "build:browser": "rimraf browser && tsc -p tsconfig.browser.json", + "build:node": "rimraf cjs && tsc -p tsconfig.cjs.json", + "build:node:esm": "rimraf esm && tsc -p tsconfig.esm.json", + "check-ts": "tsc -p tsconfig.browser.json --noEmit && tsc -p tsconfig.cjs.json --noEmit && yarn -s tslint", "clean-deps": "rimraf node_modules", "postinstall": "patch-package", "lint": "eslint --ext .js,.jsx,.ts,.tsx,.json, .", "test": "yarn test-unit", "test-debug": "vitest --inspect-brk --no-file-parallelism --test-timeout=0 --hook-timeout=0", "test-unit": "vitest", - "tslint": "tslint --config ../ts/tslint.json --project ." + "tslint": "tslint --config ../ts/tslint.json --project .", + "watch": "yarn build:browser -- -w & yarn build:node -- -w" }, "dependencies": { "component-emitter": "1.3.0", @@ -32,14 +37,16 @@ "cross-env": "7.0.3", "devtools-protocol": "0.0.1459876", "resolve-pkg": "2.0.0", + "rimraf": "6.0.1", "vitest": "^3.2.4" }, "files": [ - "index.js", - "lib", + "browser/", + "cjs/", + "esm/", "patches" ], - "types": "lib/socket.ts", + "module": "esm/node/index.js", "workspaces": { "nohoist": [ "devtools-protocol", diff --git a/packages/socket/test/socket.spec.ts b/packages/socket/test/socket.spec.ts index 4b2daaa3e6..7dbb1f7231 100644 --- a/packages/socket/test/socket.spec.ts +++ b/packages/socket/test/socket.spec.ts @@ -3,19 +3,14 @@ import fs from 'fs' import path from 'path' import parser from 'socket.io-parser' import { hasBinary } from 'socket.io-parser/dist/is-binary' -// @ts-expect-error import pkg from '../package.json' -import lib from '../index' -import * as browserLib from '../lib/browser' +import * as lib from '../lib/node' +import * as browserLib from '../lib/client' import resolvePkg from 'resolve-pkg' const { PacketType } = parser describe('Socket', function () { - it('exports server', function () { - expect(lib.server).toBeDefined() - }) - it('exports client from lib/browser', function () { expect(browserLib.client).toBeDefined() }) diff --git a/packages/socket/tsconfig.browser.json b/packages/socket/tsconfig.browser.json new file mode 100644 index 0000000000..6a99072f41 --- /dev/null +++ b/packages/socket/tsconfig.browser.json @@ -0,0 +1,18 @@ +{ + "include": [ + "lib/client/**/*.ts", + "lib/utils.ts" + ], + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDirs": [ + "./lib/client", + "./lib/utils" + ], + "outDir": "./browser", + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "node", + "declaration": true + } +} \ No newline at end of file diff --git a/packages/socket/tsconfig.cjs.json b/packages/socket/tsconfig.cjs.json new file mode 100644 index 0000000000..92e0c10a10 --- /dev/null +++ b/packages/socket/tsconfig.cjs.json @@ -0,0 +1,18 @@ +{ + "include": [ + "lib/node/**/*.ts", + "lib/utils.ts" + ], + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDirs": [ + "./lib/node", + "./lib/utils" + ], + "outDir": "./cjs", + "target": "ES2022", + "module": "CommonJS", + "moduleResolution": "node", + "declaration": true + } +} \ No newline at end of file diff --git a/packages/socket/tsconfig.esm.json b/packages/socket/tsconfig.esm.json new file mode 100644 index 0000000000..c172dec546 --- /dev/null +++ b/packages/socket/tsconfig.esm.json @@ -0,0 +1,19 @@ +{ + "include": [ + "lib/node/**/*.ts", + "lib/utils.ts" + ], + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDirs": [ + "./lib/node", + "./lib/utils" + ], + "outDir": "./esm", + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "node", + "declaration": true, + "noEmit": true + } +} \ No newline at end of file diff --git a/packages/socket/tsconfig.json b/packages/socket/tsconfig.json index 94523723d3..4c91571dd3 100644 --- a/packages/socket/tsconfig.json +++ b/packages/socket/tsconfig.json @@ -1,13 +1,14 @@ { - "extends": "./../ts/tsconfig.json", - "compilerOptions": { - "noImplicitAny": true, - "skipLibCheck": false - }, "include": [ - "lib/*.ts", + "lib/**/*.ts" ], - "files": [ - "./../ts/index.d.ts" - ] -} \ No newline at end of file + "compilerOptions": { + "allowJs": false, + "noImplicitAny": true, + "noImplicitReturns": false, + "noUncheckedIndexedAccess": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "esModuleInterop": true + } +} diff --git a/system-tests/projects/non-proxied/cypress.config.js b/system-tests/projects/non-proxied/cypress.config.js index 224732b692..7a2b3904c1 100644 --- a/system-tests/projects/non-proxied/cypress.config.js +++ b/system-tests/projects/non-proxied/cypress.config.js @@ -1,7 +1,7 @@ const { expect } = require('chai') const HttpsProxyAgent = require('https-proxy-agent') const os = require('os') -const socketIo = require('@packages/socket/lib/browser') +const socketIo = require('@packages/socket/browser/client') module.exports = { 'e2e': { diff --git a/tooling/v8-snapshot/cache/darwin/snapshot-meta.json b/tooling/v8-snapshot/cache/darwin/snapshot-meta.json index 2b98bd9e9d..0bc0c1ecd7 100644 --- a/tooling/v8-snapshot/cache/darwin/snapshot-meta.json +++ b/tooling/v8-snapshot/cache/darwin/snapshot-meta.json @@ -49,7 +49,7 @@ "./node_modules/signal-exit/index.js", "./node_modules/stream-parser/node_modules/debug/src/node.js", "./node_modules/ws/lib/websocket.js", - "./packages/https-proxy/lib/ca.js", + "./packages/https-proxy/cjs/ca.js", "./packages/proxy/lib/http/util/prerequests.ts", "./packages/server/lib/browsers/index.ts", "./packages/server/lib/browsers/memory/index.ts", @@ -770,8 +770,7 @@ "./packages/errors/cjs/index.js", "./packages/errors/cjs/stackUtils.js", "./packages/errors/node_modules/chalk/source/index.js", - "./packages/https-proxy/index.js", - "./packages/https-proxy/test/helpers/certs.js", + "./packages/https-proxy/cjs/server.js", "./packages/icons/index.js", "./packages/launcher/lib/browsers.ts", "./packages/net-stubbing/lib/internal-types.ts", @@ -822,6 +821,7 @@ "./packages/server/lib/cloud/studio/StudioLifecycleManager.ts", "./packages/server/lib/cloud/user.ts", "./packages/server/lib/config.ts", + "./packages/server/lib/controllers/client.ts", "./packages/server/lib/cypress.ts", "./packages/server/lib/environment.js", "./packages/server/lib/modes/record.ts", @@ -900,9 +900,8 @@ "./packages/server/node_modules/tough-cookie/lib/cookie.js", "./packages/server/start-cypress.js", "./packages/server/v8-snapshot-entry.js", - "./packages/socket/index.js", - "./packages/socket/lib/cdp-socket.ts", - "./packages/socket/lib/socket.ts", + "./packages/socket/cjs/node/cdp-socket.js", + "./packages/socket/cjs/node/socket.js", "./packages/socket/node_modules/socket.io-parser/node_modules/debug/src/browser.js", "./packages/socket/node_modules/socket.io-parser/node_modules/debug/src/index.js", "./packages/socket/node_modules/socket.io/dist/broadcast-operator.js", @@ -3513,7 +3512,6 @@ "./node_modules/source-map/source-map.js", "./node_modules/squirrelly/dist/squirrelly.min.js", "./node_modules/sshpk/lib/ssh-buffer.js", - "./node_modules/ssl-root-cas/ssl-root-cas.js", "./node_modules/statuses/codes.json", "./node_modules/statuses/index.js", "./node_modules/stealthy-require/lib/index.js", @@ -3925,10 +3923,9 @@ "./packages/frontend-shared/cypress/e2e/prod-dependencies.ts", "./packages/frontend-shared/cypress/e2e/v8-snapshot-entry.ts", "./packages/frontend-shared/node_modules/cross-fetch/dist/node-ponyfill.js", - "./packages/https-proxy/lib/proxy.js", - "./packages/https-proxy/lib/server.js", - "./packages/https-proxy/lib/util/parse.js", - "./packages/https-proxy/test/helpers/https_server.js", + "./packages/https-proxy/cjs/index.js", + "./packages/https-proxy/cjs/proxy.js", + "./packages/https-proxy/cjs/util/parse.js", "./packages/launcher/index.ts", "./packages/launcher/lib/darwin/index.ts", "./packages/launcher/lib/darwin/util.ts", @@ -4094,7 +4091,6 @@ "./packages/server/lib/cloud/upload/stream_activity_monitor.ts", "./packages/server/lib/cloud/upload/stream_stalled_error.ts", "./packages/server/lib/cohorts.ts", - "./packages/server/lib/controllers/client.ts", "./packages/server/lib/controllers/files.ts", "./packages/server/lib/controllers/iframes.ts", "./packages/server/lib/controllers/runner.ts", @@ -4292,7 +4288,8 @@ "./packages/server/node_modules/whatwg-url/lib/public-api.js", "./packages/server/node_modules/whatwg-url/lib/url-state-machine.js", "./packages/server/node_modules/whatwg-url/lib/utils.js", - "./packages/socket/lib/utils.ts", + "./packages/socket/cjs/node/index.js", + "./packages/socket/cjs/utils.js", "./packages/socket/node_modules/engine.io-parser/lib/commons.js", "./packages/socket/node_modules/engine.io-parser/lib/decodePacket.js", "./packages/socket/node_modules/engine.io-parser/lib/encodePacket.js", @@ -4358,5 +4355,5 @@ "./tooling/v8-snapshot/cache/darwin/snapshot-entry.js" ], "deferredHashFile": "yarn.lock", - "deferredHash": "885be5a2d8a0d33f35972325ea7ef0467933f610940486ce6add9be70dfb8dc5" + "deferredHash": "97bd6156ae0360b4fbb5f54dc111320dbba6f63bd0bdecb0ff8dbc8ee601d757" } \ No newline at end of file diff --git a/tooling/v8-snapshot/cache/linux/snapshot-meta.json b/tooling/v8-snapshot/cache/linux/snapshot-meta.json index fb2d210f2d..c35316da00 100644 --- a/tooling/v8-snapshot/cache/linux/snapshot-meta.json +++ b/tooling/v8-snapshot/cache/linux/snapshot-meta.json @@ -49,7 +49,7 @@ "./node_modules/signal-exit/index.js", "./node_modules/stream-parser/node_modules/debug/src/node.js", "./node_modules/ws/lib/websocket.js", - "./packages/https-proxy/lib/ca.js", + "./packages/https-proxy/cjs/ca.js", "./packages/proxy/lib/http/util/prerequests.ts", "./packages/server/lib/browsers/index.ts", "./packages/server/lib/browsers/memory/index.ts", @@ -769,8 +769,7 @@ "./packages/errors/cjs/index.js", "./packages/errors/cjs/stackUtils.js", "./packages/errors/node_modules/chalk/source/index.js", - "./packages/https-proxy/index.js", - "./packages/https-proxy/test/helpers/certs.js", + "./packages/https-proxy/cjs/server.js", "./packages/icons/index.js", "./packages/launcher/lib/browsers.ts", "./packages/net-stubbing/lib/internal-types.ts", @@ -821,6 +820,7 @@ "./packages/server/lib/cloud/studio/StudioLifecycleManager.ts", "./packages/server/lib/cloud/user.ts", "./packages/server/lib/config.ts", + "./packages/server/lib/controllers/client.ts", "./packages/server/lib/cypress.ts", "./packages/server/lib/environment.js", "./packages/server/lib/modes/record.ts", @@ -899,9 +899,8 @@ "./packages/server/node_modules/tough-cookie/lib/cookie.js", "./packages/server/start-cypress.js", "./packages/server/v8-snapshot-entry.js", - "./packages/socket/index.js", - "./packages/socket/lib/cdp-socket.ts", - "./packages/socket/lib/socket.ts", + "./packages/socket/cjs/node/cdp-socket.js", + "./packages/socket/cjs/node/socket.js", "./packages/socket/node_modules/socket.io-parser/node_modules/debug/src/browser.js", "./packages/socket/node_modules/socket.io-parser/node_modules/debug/src/index.js", "./packages/socket/node_modules/socket.io/dist/broadcast-operator.js", @@ -3512,7 +3511,6 @@ "./node_modules/source-map/source-map.js", "./node_modules/squirrelly/dist/squirrelly.min.js", "./node_modules/sshpk/lib/ssh-buffer.js", - "./node_modules/ssl-root-cas/ssl-root-cas.js", "./node_modules/statuses/codes.json", "./node_modules/statuses/index.js", "./node_modules/stealthy-require/lib/index.js", @@ -3924,10 +3922,9 @@ "./packages/frontend-shared/cypress/e2e/prod-dependencies.ts", "./packages/frontend-shared/cypress/e2e/v8-snapshot-entry.ts", "./packages/frontend-shared/node_modules/cross-fetch/dist/node-ponyfill.js", - "./packages/https-proxy/lib/proxy.js", - "./packages/https-proxy/lib/server.js", - "./packages/https-proxy/lib/util/parse.js", - "./packages/https-proxy/test/helpers/https_server.js", + "./packages/https-proxy/cjs/index.js", + "./packages/https-proxy/cjs/proxy.js", + "./packages/https-proxy/cjs/util/parse.js", "./packages/launcher/index.ts", "./packages/launcher/lib/darwin/index.ts", "./packages/launcher/lib/darwin/util.ts", @@ -4093,7 +4090,6 @@ "./packages/server/lib/cloud/upload/stream_activity_monitor.ts", "./packages/server/lib/cloud/upload/stream_stalled_error.ts", "./packages/server/lib/cohorts.ts", - "./packages/server/lib/controllers/client.ts", "./packages/server/lib/controllers/files.ts", "./packages/server/lib/controllers/iframes.ts", "./packages/server/lib/controllers/runner.ts", @@ -4291,7 +4287,8 @@ "./packages/server/node_modules/whatwg-url/lib/public-api.js", "./packages/server/node_modules/whatwg-url/lib/url-state-machine.js", "./packages/server/node_modules/whatwg-url/lib/utils.js", - "./packages/socket/lib/utils.ts", + "./packages/socket/cjs/node/index.js", + "./packages/socket/cjs/utils.js", "./packages/socket/node_modules/engine.io-parser/lib/commons.js", "./packages/socket/node_modules/engine.io-parser/lib/decodePacket.js", "./packages/socket/node_modules/engine.io-parser/lib/encodePacket.js", @@ -4357,5 +4354,5 @@ "./tooling/v8-snapshot/cache/linux/snapshot-entry.js" ], "deferredHashFile": "yarn.lock", - "deferredHash": "885be5a2d8a0d33f35972325ea7ef0467933f610940486ce6add9be70dfb8dc5" + "deferredHash": "97bd6156ae0360b4fbb5f54dc111320dbba6f63bd0bdecb0ff8dbc8ee601d757" } \ No newline at end of file diff --git a/tooling/v8-snapshot/cache/win32/snapshot-meta.json b/tooling/v8-snapshot/cache/win32/snapshot-meta.json index ac80347b74..5fd4026889 100644 --- a/tooling/v8-snapshot/cache/win32/snapshot-meta.json +++ b/tooling/v8-snapshot/cache/win32/snapshot-meta.json @@ -49,7 +49,7 @@ "./node_modules/signal-exit/index.js", "./node_modules/stream-parser/node_modules/debug/src/node.js", "./node_modules/ws/lib/websocket.js", - "./packages/https-proxy/lib/ca.js", + "./packages/https-proxy/cjs/ca.js", "./packages/proxy/lib/http/util/prerequests.ts", "./packages/server/lib/browsers/index.ts", "./packages/server/lib/browsers/memory/index.ts", @@ -776,8 +776,7 @@ "./packages/errors/cjs/index.js", "./packages/errors/cjs/stackUtils.js", "./packages/errors/node_modules/chalk/source/index.js", - "./packages/https-proxy/index.js", - "./packages/https-proxy/test/helpers/certs.js", + "./packages/https-proxy/cjs/server.js", "./packages/icons/index.js", "./packages/launcher/lib/browsers.ts", "./packages/net-stubbing/lib/internal-types.ts", @@ -828,6 +827,7 @@ "./packages/server/lib/cloud/studio/StudioLifecycleManager.ts", "./packages/server/lib/cloud/user.ts", "./packages/server/lib/config.ts", + "./packages/server/lib/controllers/client.ts", "./packages/server/lib/cypress.ts", "./packages/server/lib/environment.js", "./packages/server/lib/modes/record.ts", @@ -906,9 +906,8 @@ "./packages/server/node_modules/tough-cookie/lib/cookie.js", "./packages/server/start-cypress.js", "./packages/server/v8-snapshot-entry.js", - "./packages/socket/index.js", - "./packages/socket/lib/cdp-socket.ts", - "./packages/socket/lib/socket.ts", + "./packages/socket/cjs/node/cdp-socket.js", + "./packages/socket/cjs/node/socket.js", "./packages/socket/node_modules/socket.io-parser/node_modules/debug/src/browser.js", "./packages/socket/node_modules/socket.io-parser/node_modules/debug/src/index.js", "./packages/socket/node_modules/socket.io/dist/broadcast-operator.js", @@ -3515,7 +3514,6 @@ "./node_modules/source-map/source-map.js", "./node_modules/squirrelly/dist/squirrelly.min.js", "./node_modules/sshpk/lib/ssh-buffer.js", - "./node_modules/ssl-root-cas/ssl-root-cas.js", "./node_modules/statuses/codes.json", "./node_modules/statuses/index.js", "./node_modules/stealthy-require/lib/index.js", @@ -3924,10 +3922,9 @@ "./packages/frontend-shared/cypress/e2e/prod-dependencies.ts", "./packages/frontend-shared/cypress/e2e/v8-snapshot-entry.ts", "./packages/frontend-shared/node_modules/cross-fetch/dist/node-ponyfill.js", - "./packages/https-proxy/lib/proxy.js", - "./packages/https-proxy/lib/server.js", - "./packages/https-proxy/lib/util/parse.js", - "./packages/https-proxy/test/helpers/https_server.js", + "./packages/https-proxy/cjs/index.js", + "./packages/https-proxy/cjs/proxy.js", + "./packages/https-proxy/cjs/util/parse.js", "./packages/launcher/index.ts", "./packages/launcher/lib/darwin/index.ts", "./packages/launcher/lib/darwin/util.ts", @@ -4093,7 +4090,6 @@ "./packages/server/lib/cloud/upload/stream_activity_monitor.ts", "./packages/server/lib/cloud/upload/stream_stalled_error.ts", "./packages/server/lib/cohorts.ts", - "./packages/server/lib/controllers/client.ts", "./packages/server/lib/controllers/files.ts", "./packages/server/lib/controllers/iframes.ts", "./packages/server/lib/controllers/runner.ts", @@ -4291,7 +4287,8 @@ "./packages/server/node_modules/whatwg-url/lib/public-api.js", "./packages/server/node_modules/whatwg-url/lib/url-state-machine.js", "./packages/server/node_modules/whatwg-url/lib/utils.js", - "./packages/socket/lib/utils.ts", + "./packages/socket/cjs/node/index.js", + "./packages/socket/cjs/utils.js", "./packages/socket/node_modules/engine.io-parser/lib/commons.js", "./packages/socket/node_modules/engine.io-parser/lib/decodePacket.js", "./packages/socket/node_modules/engine.io-parser/lib/encodePacket.js", @@ -4357,5 +4354,5 @@ "./tooling/v8-snapshot/cache/win32/snapshot-entry.js" ], "deferredHashFile": "yarn.lock", - "deferredHash": "1535a3c7da63f962c611674f06b1c6e54c2deec1d80d898c2931954ccdc8419f" + "deferredHash": "6040eb3f79693bc3c5dbd0146ebecab6a11d0347d924df45e667964c55249d2b" } \ No newline at end of file From 50ceb5a924a0b0f993c8c92f7f24faef9d9cd854 Mon Sep 17 00:00:00 2001 From: Bill Glesias Date: Thu, 23 Oct 2025 10:32:36 -0400 Subject: [PATCH 2/3] chore: migrate `@packages/extension` to `TypeScript` and tests to `Vitest` (#32680) * chore: convert extension to TypeScript and vitest * chore: typecheck the whole package --- .circleci/src/pipeline/@pipeline.yml | 2 +- guides/esm-migration.md | 4 +- packages/extension/.gitignore | 2 + packages/extension/README.md | 8 + .../app/v2/{background.js => background.ts} | 39 ++- packages/extension/app/v2/client.ts | 2 +- .../extension/app/v2/{init.js => init.ts} | 4 +- .../app/v3/{content.js => content.ts} | 2 +- .../{service-worker.js => service-worker.ts} | 13 +- packages/extension/gulpfile.ts | 83 +++--- packages/extension/index.d.ts | 5 - packages/extension/index.js | 1 - packages/extension/lib/extension.js | 38 --- packages/extension/lib/index.ts | 30 ++ packages/extension/lib/util.js | 10 - packages/extension/package.json | 28 +- .../test/integration/v2/background.spec.ts | 275 ++++++++++++++++++ .../test/integration/v2/background_spec.js | 244 ---------------- .../test/integration/v3/content.spec.ts | 124 ++++++++ .../test/integration/v3/content_spec.js | 108 ------- .../integration/v3/service-worker.spec.ts | 157 ++++++++++ .../integration/v3/service-worker_spec.js | 137 --------- packages/extension/test/mocha.opts | 4 - packages/extension/test/spec_helper.js | 12 - .../extension/test/unit/extension.spec.ts | 111 +++++++ .../extension/test/unit/extension_spec.js | 136 --------- packages/extension/tsconfig.app.v2.json | 12 + packages/extension/tsconfig.app.v3.json | 12 + packages/extension/tsconfig.json | 16 +- packages/extension/tsconfig.lib.json | 13 + packages/extension/vitest.config.ts | 9 + .../{webpack.config.js => webpack.config.mjs} | 19 +- packages/proxy/lib/http/error-middleware.ts | 2 +- packages/server/lib/automation/cookies.ts | 18 +- packages/server/lib/browsers/chrome.ts | 2 +- .../server/test/unit/browsers/firefox_spec.ts | 2 +- yarn.lock | 78 ++++- 37 files changed, 945 insertions(+), 817 deletions(-) create mode 100644 packages/extension/.gitignore rename packages/extension/app/v2/{background.js => background.ts} (72%) rename packages/extension/app/v2/{init.js => init.ts} (53%) rename packages/extension/app/v3/{content.js => content.ts} (95%) rename packages/extension/app/v3/{service-worker.js => service-worker.ts} (83%) delete mode 100644 packages/extension/index.d.ts delete mode 100644 packages/extension/index.js delete mode 100644 packages/extension/lib/extension.js create mode 100644 packages/extension/lib/index.ts delete mode 100644 packages/extension/lib/util.js create mode 100644 packages/extension/test/integration/v2/background.spec.ts delete mode 100644 packages/extension/test/integration/v2/background_spec.js create mode 100644 packages/extension/test/integration/v3/content.spec.ts delete mode 100644 packages/extension/test/integration/v3/content_spec.js create mode 100644 packages/extension/test/integration/v3/service-worker.spec.ts delete mode 100644 packages/extension/test/integration/v3/service-worker_spec.js delete mode 100644 packages/extension/test/mocha.opts delete mode 100644 packages/extension/test/spec_helper.js create mode 100644 packages/extension/test/unit/extension.spec.ts delete mode 100644 packages/extension/test/unit/extension_spec.js create mode 100644 packages/extension/tsconfig.app.v2.json create mode 100644 packages/extension/tsconfig.app.v3.json create mode 100644 packages/extension/tsconfig.lib.json create mode 100644 packages/extension/vitest.config.ts rename packages/extension/{webpack.config.js => webpack.config.mjs} (69%) diff --git a/.circleci/src/pipeline/@pipeline.yml b/.circleci/src/pipeline/@pipeline.yml index 6f01dfd298..8a8125567c 100644 --- a/.circleci/src/pipeline/@pipeline.yml +++ b/.circleci/src/pipeline/@pipeline.yml @@ -1785,7 +1785,7 @@ jobs: source ./scripts/ensure-node.sh yarn lerna run types - sanitize-verify-and-store-mocha-results: - expectedResultCount: 5 + expectedResultCount: 4 verify-release-readiness: <<: *defaults diff --git a/guides/esm-migration.md b/guides/esm-migration.md index ca3212ad82..0617d4c683 100644 --- a/guides/esm-migration.md +++ b/guides/esm-migration.md @@ -48,7 +48,7 @@ When migrating some of these projects away from the `ts-node` entry [see `@packa - [x] packages/error ✅ **COMPLETED** - [x] packages/eslint-config ✅ **COMPLETED** - [ ] packages/example -- [ ] packages/extension +- [x] packages/extension ✅ **COMPLETED** - [ ] packages/frontend-shared **PARTIAL** - entry point is JS - [x] packages/electron ✅ **COMPLETED** - [x] packages/https-proxy - ✅ **COMPLETED** @@ -99,7 +99,7 @@ When migrating some of these projects away from the `ts-node` entry [see `@packa - [x] packages/driver ✅ **COMPLETED** - [x] packages/electron ✅ **COMPLETED** - [x] packages/error ✅ **COMPLETED** -- [ ] packages/extension +- [x] packages/extension ✅ **COMPLETED** - [x] packages/https-proxy ✅ **COMPLETED** - [x] packages/electron ✅ **COMPLETED** - [x] packages/icons ✅ **COMPLETED** diff --git a/packages/extension/.gitignore b/packages/extension/.gitignore new file mode 100644 index 0000000000..b7364c5b38 --- /dev/null +++ b/packages/extension/.gitignore @@ -0,0 +1,2 @@ +app-dist/ +lib-dist/ \ No newline at end of file diff --git a/packages/extension/README.md b/packages/extension/README.md index ad1409fa7b..3e63dd1355 100644 --- a/packages/extension/README.md +++ b/packages/extension/README.md @@ -6,12 +6,20 @@ This is the WebExtension responsible for automating the browser ### Watching +Kicks off the gulp watcher that rebuilds the app/lib directories on change. + ```bash yarn workspace @packages/extension watch ``` ## Building +`@packages/extension` has a few different build processes occurring that are all driven by the [`gulpfile`](./gulpfile.ts). +* `app` - The web extension piece of the code, has two separate bundles: + * `v2`: Version 2 of the web extension which uses [webpack](./webpack.config.mjs) to bundle the `app/v2` directory and output it as `background.js` + * `v3`: Version 3 of the web extension, which doesn't have any external dependencies so we are able to compile down to `ESM to run in the browser natively. +* `lib` - the `@packages/extension` `main` entry that has utility methods on how to find/load the extension. This is transpiled to CommonJS as it is consumed in the Node context. + ```bash yarn workspace @packages/extension build ``` diff --git a/packages/extension/app/v2/background.js b/packages/extension/app/v2/background.ts similarity index 72% rename from packages/extension/app/v2/background.js rename to packages/extension/app/v2/background.ts index c7d91c5acc..ac33e2533c 100644 --- a/packages/extension/app/v2/background.js +++ b/packages/extension/app/v2/background.ts @@ -1,9 +1,9 @@ -const get = require('lodash/get') -const once = require('lodash/once') -const Promise = require('bluebird') -const browser = require('webextension-polyfill') +import get from 'lodash/get' +import once from 'lodash/once' +import Bluebird from 'bluebird' +import browser from 'webextension-polyfill' -const client = require('./client') +import { connect as clientConnect } from './client' const checkIfFirefox = async () => { if (!browser || !get(browser, 'runtime.getBrowserInfo')) { @@ -15,9 +15,9 @@ const checkIfFirefox = async () => { return name === 'Firefox' } -const connect = function (host, path, extraOpts) { +const connect = function (host: string, path: string, extraOpts?: any) { const listenToCookieChanges = once(() => { - return browser.cookies.onChanged.addListener((info) => { + return browser.cookies.onChanged.addListener((info: any) => { if (info.cause !== 'overwrite') { return ws.emit('automation:push:request', 'change:cookie', info) } @@ -25,7 +25,7 @@ const connect = function (host, path, extraOpts) { }) const listenToDownloads = once(() => { - browser.downloads.onCreated.addListener((downloadItem) => { + browser.downloads.onCreated.addListener((downloadItem: any) => { ws.emit('automation:push:request', 'create:download', { id: `${downloadItem.id}`, filePath: downloadItem.filename, @@ -34,7 +34,7 @@ const connect = function (host, path, extraOpts) { }) }) - browser.downloads.onChanged.addListener((downloadDelta) => { + browser.downloads.onChanged.addListener((downloadDelta: any) => { const state = (downloadDelta.state || {}).current if (state === 'complete') { @@ -51,7 +51,7 @@ const connect = function (host, path, extraOpts) { }) }) - const fail = (id, err) => { + const fail = (id: number, err: any) => { return ws.emit('automation:response', id, { __error: err.message, __stack: err.stack, @@ -59,21 +59,22 @@ const connect = function (host, path, extraOpts) { }) } - const invoke = function (method, id, ...args) { - const respond = (data) => { + const invoke = function (method: string, id: number, ...args: any[]) { + const respond = (data: any) => { return ws.emit('automation:response', id, { response: data }) } - return Promise.try(() => { + return Bluebird.try(() => { + // @ts-expect-error return automation[method].apply(automation, args.concat(respond)) }).catch((err) => { return fail(id, err) }) } - const ws = client.connect(host, path, extraOpts) + const ws = clientConnect(host, path, extraOpts) - ws.on('automation:request', (id, msg, data) => { + ws.on('automation:request', (id: number, msg: string, data: any) => { switch (msg) { case 'reset:browser:state': return invoke('resetBrowserState', id) @@ -82,7 +83,7 @@ const connect = function (host, path, extraOpts) { } }) - ws.on('automation:config', async (config) => { + ws.on('automation:config', async (config: any) => { const isFirefox = await checkIfFirefox() listenToCookieChanges() @@ -99,15 +100,13 @@ const connect = function (host, path, extraOpts) { return ws } -const automation = { +export const automation = { connect, - resetBrowserState (fn) { + resetBrowserState (fn: any) { // We remove browser data. Firefox goes through this path, while chrome goes through cdp automation // Note that firefox does not support fileSystems or serverBoundCertificates // (https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/browsingData/DataTypeSet). return browser.browsingData.remove({}, { cache: true, cookies: true, downloads: true, formData: true, history: true, indexedDB: true, localStorage: true, passwords: true, pluginData: true, serviceWorkers: true }).then(fn) }, } - -module.exports = automation diff --git a/packages/extension/app/v2/client.ts b/packages/extension/app/v2/client.ts index fadcab51ce..d87e59e5ba 100644 --- a/packages/extension/app/v2/client.ts +++ b/packages/extension/app/v2/client.ts @@ -1,6 +1,6 @@ import { client } from '@packages/socket/browser/client' -export const connect = (host, path, extraOpts = {}) => { +export const connect = (host: string, path: string, extraOpts: any = {}) => { return client(host, { path, transports: ['websocket'], diff --git a/packages/extension/app/v2/init.js b/packages/extension/app/v2/init.ts similarity index 53% rename from packages/extension/app/v2/init.js rename to packages/extension/app/v2/init.ts index 8652449260..0444635243 100644 --- a/packages/extension/app/v2/init.js +++ b/packages/extension/app/v2/init.ts @@ -1,7 +1,7 @@ -const background = require('./background') +import { automation } from './background' const HOST = 'CHANGE_ME_HOST' const PATH = 'CHANGE_ME_PATH' // immediately connect -background.connect(HOST, PATH) +automation.connect(HOST, PATH) diff --git a/packages/extension/app/v3/content.js b/packages/extension/app/v3/content.ts similarity index 95% rename from packages/extension/app/v3/content.js rename to packages/extension/app/v3/content.ts index 8c34f18b27..19f1e21698 100644 --- a/packages/extension/app/v3/content.js +++ b/packages/extension/app/v3/content.ts @@ -26,7 +26,7 @@ window.addEventListener('message', ({ data, source }) => { }) // this listens for messages from the background service worker script -port.onMessage.addListener(({ message }) => { +port.onMessage.addListener(({ message }: { message: string }) => { // this lets us know the message we sent to the background script to activate // the main tab was successful, so we in turn send it on to Cypress // via postMessage diff --git a/packages/extension/app/v3/service-worker.js b/packages/extension/app/v3/service-worker.ts similarity index 83% rename from packages/extension/app/v3/service-worker.js rename to packages/extension/app/v3/service-worker.ts index cbc47dc536..26205daaf3 100644 --- a/packages/extension/app/v3/service-worker.js +++ b/packages/extension/app/v3/service-worker.ts @@ -1,4 +1,4 @@ -/* global chrome */ +declare let chrome: any // this background script runs in a service worker. it has access to the // extension API, but not direct access the web page or anything else @@ -9,10 +9,9 @@ // go to `chrome://extensions` and hit the reload button under the Cypress // extension. sometimes that doesn't work and requires re-launching Chrome // and then reloading the extension via `chrome://extensions` - -async function getFromStorage (key) { +async function getFromStorage (key: string) { return new Promise((resolve) => { - chrome.storage.local.get(key, (storage) => { + chrome.storage.local.get(key, (storage: any) => { resolve(storage[key]) }) }) @@ -23,7 +22,7 @@ async function activateMainTab () { const url = await getFromStorage('mostRecentUrl') const tabs = await chrome.tabs.query({}) - const cypressTab = tabs.find((tab) => tab.url.includes(url)) + const cypressTab = tabs.find((tab: any) => tab.url.includes(url)) if (!cypressTab) return @@ -41,8 +40,8 @@ async function activateMainTab () { // here we connect to the content script, which has access to the web page // running Cypress, but not the extension API -chrome.runtime.onConnect.addListener((port) => { - port.onMessage.addListener(async ({ message, url }) => { +chrome.runtime.onConnect.addListener((port: any) => { + port.onMessage.addListener(async ({ message, url }: { message: string, url: string }) => { if (message === 'activate:main:tab') { await activateMainTab() diff --git a/packages/extension/gulpfile.ts b/packages/extension/gulpfile.ts index ae0a257893..e485bee648 100644 --- a/packages/extension/gulpfile.ts +++ b/packages/extension/gulpfile.ts @@ -1,87 +1,82 @@ +import { promisify } from 'util' +import { exec } from 'child_process' import gulp from 'gulp' import { rimraf } from 'rimraf' -import { waitUntilIconsBuilt } from '../../scripts/ensure-icons' -import cp from 'child_process' -import * as path from 'path' +import { getPathToIcon, getPathToLogo } from '@packages/icons' -const nodeWebpack = path.join(__dirname, '..', '..', 'scripts', 'run-webpack.js') +const execAsync = promisify(exec) -async function cypressIcons () { - await waitUntilIconsBuilt() +export async function clean (): Promise { + const removedAppDist = await rimraf('app-dist') + const removedLibDist = await rimraf('lib-dist') - return require('@packages/icons') -} - -function clean (): Promise { - return rimraf('dist') + return removedAppDist && removedLibDist } const manifest = (v: 'v2' | 'v3') => { return () => { return gulp.src(`app/${v}/manifest.json`) - .pipe(gulp.dest(`dist/${v}`)) + .pipe(gulp.dest(`app-dist/${v}`)) } } -const background = (cb) => { - cp.fork(nodeWebpack, { stdio: 'inherit' }).on('exit', (code) => { - cb(code === 0 ? null : new Error(`Webpack process exited with code ${code}`)) - }) +const buildAppV2 = async () => { + await execAsync('yarn build:v2') } -const copyScriptsForV3 = () => { - return gulp.src('app/v3/*.js') - .pipe(gulp.dest('dist/v3')) +const buildAppV3 = async () => { + await execAsync('yarn build:v3') +} + +const buildLib = async () => { + await execAsync('yarn build:lib') } const html = () => { return gulp.src('app/**/*.html') - .pipe(gulp.dest('dist/v2')) - .pipe(gulp.dest('dist/v3')) + .pipe(gulp.dest('app-dist/v2')) + .pipe(gulp.dest('app-dist/v3')) } const css = () => { return gulp.src('app/**/*.css') - .pipe(gulp.dest('dist/v2')) - .pipe(gulp.dest('dist/v3')) + .pipe(gulp.dest('app-dist/v2')) + .pipe(gulp.dest('app-dist/v3')) } const icons = async () => { - const cyIcons = await cypressIcons() - return gulp.src([ - cyIcons.getPathToIcon('icon_16x16.png'), - cyIcons.getPathToIcon('icon_19x19.png'), - cyIcons.getPathToIcon('icon_38x38.png'), - cyIcons.getPathToIcon('icon_48x48.png'), - cyIcons.getPathToIcon('icon_128x128.png'), + getPathToIcon('icon_16x16.png'), + getPathToIcon('icon_19x19.png'), + getPathToIcon('icon_38x38.png'), + getPathToIcon('icon_48x48.png'), + getPathToIcon('icon_128x128.png'), ]) - .pipe(gulp.dest('dist/v2/icons')) - .pipe(gulp.dest('dist/v3/icons')) + .pipe(gulp.dest('app-dist/v2/icons')) + .pipe(gulp.dest('app-dist/v3/icons')) } const logos = async () => { - const cyIcons = await cypressIcons() - // appease TS return gulp.src([ - cyIcons.getPathToLogo('cypress-bw.png'), + getPathToLogo('cypress-bw.png'), ]) - .pipe(gulp.dest('dist/v2/logos')) - .pipe(gulp.dest('dist/v3/logos')) + .pipe(gulp.dest('app-dist/v2/logos')) + .pipe(gulp.dest('app-dist/v3/logos')) } -const build = gulp.series( +export const build = gulp.series( clean, + buildAppV2, + buildAppV3, gulp.parallel( icons, logos, manifest('v2'), manifest('v3'), - background, - copyScriptsForV3, html, css, + buildLib, ), ) @@ -89,10 +84,4 @@ const watchBuild = () => { return gulp.watch('app/**/*', build) } -const watch = gulp.series(build, watchBuild) - -module.exports = { - build, - clean, - watch, -} +export const watch = gulp.series(build, watchBuild) diff --git a/packages/extension/index.d.ts b/packages/extension/index.d.ts deleted file mode 100644 index 902c9e1995..0000000000 --- a/packages/extension/index.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// - -declare const lib: typeof import('./lib/extension') - -export default lib \ No newline at end of file diff --git a/packages/extension/index.js b/packages/extension/index.js deleted file mode 100644 index 35ee4527ed..0000000000 --- a/packages/extension/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./lib/extension') diff --git a/packages/extension/lib/extension.js b/packages/extension/lib/extension.js deleted file mode 100644 index 75612fe790..0000000000 --- a/packages/extension/lib/extension.js +++ /dev/null @@ -1,38 +0,0 @@ -const path = require('path') -const Promise = require('bluebird') -const { getCookieUrl } = require('./util') -const fs = Promise.promisifyAll(require('fs')) - -module.exports = { - getPathToExtension (...args) { - args = [__dirname, '..', 'dist', 'v2'].concat(args) - - return path.join.apply(path, args) - }, - - getPathToV3Extension (...args) { - return path.join(...[__dirname, '..', 'dist', 'v3', ...args]) - }, - - getPathToTheme () { - return path.join(__dirname, '..', 'theme') - }, - - getPathToRoot () { - return path.join(__dirname, '..') - }, - - setHostAndPath (host, path) { - const src = this.getPathToExtension('background.js') - - return fs.readFileAsync(src, 'utf8') - .then((str) => { - return str - .replace('CHANGE_ME_HOST', host) - .replace('CHANGE_ME_PATH', path) - }) - }, - - getCookieUrl, - -} diff --git a/packages/extension/lib/index.ts b/packages/extension/lib/index.ts new file mode 100644 index 0000000000..df2007342d --- /dev/null +++ b/packages/extension/lib/index.ts @@ -0,0 +1,30 @@ +import path from 'path' +import { readFile } from 'fs/promises' + +export const getPathToExtension = (...args: string[]) => { + args = [__dirname, '..', 'app-dist', 'v2'].concat(args) + + return path.join.apply(path, args) +} + +export const getPathToV3Extension = (...args: string[]) => { + return path.join(...[__dirname, '..', 'app-dist', 'v3', ...args]) +} + +export const getPathToTheme = () => { + return path.join(__dirname, '..', 'theme') +} + +export const getPathToRoot = () => { + return path.join(__dirname, '..') +} + +export const setHostAndPath = async (host: string, path: string) => { + const src = getPathToExtension('background.js') + + const str = await readFile(src, 'utf8') + + return str + .replace('CHANGE_ME_HOST', host) + .replace('CHANGE_ME_PATH', path) +} diff --git a/packages/extension/lib/util.js b/packages/extension/lib/util.js deleted file mode 100644 index 20f6a0b9fe..0000000000 --- a/packages/extension/lib/util.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - getCookieUrl: (cookie = {}) => { - const prefix = cookie.secure ? 'https://' : 'http://' - - // https://github.com/cypress-io/cypress/issues/6375 - const host = cookie.domain.startsWith('.') ? cookie.domain.slice(1) : cookie.domain - - return prefix + host + (cookie.path || '') - }, -} diff --git a/packages/extension/package.json b/packages/extension/package.json index 350ed4dc5f..5ce66af79b 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -2,17 +2,21 @@ "name": "@packages/extension", "version": "0.0.0-development", "private": true, - "main": "index.js", + "main": "lib-dist/index.js", "scripts": { "build": "gulp build", - "check-ts": "tsc --noEmit && yarn -s tslint", + "build-prod": "yarn build", + "build:lib": "tsc -p tsconfig.lib.json", + "build:v2": "webpack-cli", + "build:v3": "tsc -p tsconfig.app.v3.json", + "check-ts": "tsc -p tsconfig.json --noEmit && yarn -s tslint -p tsconfig.json", "clean": "gulp clean", "clean-deps": "rimraf node_modules", "postinstall": "echo '@packages/extension needs: yarn build'", "lint": "eslint --ext .js,.jsx,.ts,.tsx,.json, .", "test": "yarn test-unit", - "test-debug": "yarn test-unit --inspect-brk=5566", - "test-unit": "cross-env NODE_ENV=test mocha -r @packages/ts/register --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json", + "test-debug": "vitest --inspect-brk --no-file-parallelism --test-timeout=0 --hook-timeout=0", + "test-unit": "vitest run", "test-watch": "yarn test-unit --watch", "tslint": "tslint --config ../ts/tslint.json --project . --exclude ./dist/v2/background.js", "watch": "yarn build && chokidar 'app/**/*.*' 'app/*.*' -c 'yarn build'" @@ -24,25 +28,21 @@ "devDependencies": { "@packages/icons": "0.0.0-development", "@packages/socket": "0.0.0-development", - "chai": "3.5.0", + "@types/webextension-polyfill": "0.12.4", "chokidar-cli": "2.1.0", "cross-env": "7.0.3", - "eol": "0.10.0", "fs-extra": "9.1.0", "gulp": "4.0.2", - "mocha": "3.5.3", - "mock-require": "3.0.3", "rimraf": "6.0.1", - "sinon": "7.3.2", - "sinon-chai": "3.7.0", "ts-loader": "9.5.2", + "vitest": "^3.2.4", "webextension-polyfill": "0.4.0", - "webpack": "^5.88.2" + "webpack": "^5.88.2", + "webpack-cli": "^6.0.1" }, "files": [ - "app", - "dist", - "lib", + "app-dist", + "lib-dist", "theme" ], "nx": { diff --git a/packages/extension/test/integration/v2/background.spec.ts b/packages/extension/test/integration/v2/background.spec.ts new file mode 100644 index 0000000000..8a92347605 --- /dev/null +++ b/packages/extension/test/integration/v2/background.spec.ts @@ -0,0 +1,275 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' +import _ from 'lodash' +import http from 'http' +import { SocketIOServer } from '@packages/socket' +import { connect } from '../../../app/v2/client' +import EventEmitter from 'events' +import type { SocketShape } from '@packages/socket/browser/client' +import browser from 'webextension-polyfill' +import { automation } from '../../../app/v2/background' + +vi.mock('../../../app/v2/client', async (importActual) => { + const actual = await importActual() + + return { + // @ts-expect-error + ...actual, + connect: vi.fn(), + } +}) + +vi.mock('webextension-polyfill', () => { + return { + default: { + cookies: { + onChanged: { + addListener: vi.fn(), + }, + }, + downloads: { + onCreated: { + addListener: vi.fn(), + }, + onChanged: { + addListener: vi.fn(), + }, + }, + runtime: {}, + browsingData: { + remove: vi.fn(), + }, + }, + } +}) + +const PORT = 12345 + +describe('app/background', () => { + let httpSrv: http.Server + let server: http.Server + let connectWrapper: (options?: Record) => Promise + + beforeEach(async () => { + vi.resetAllMocks() + vi.stubGlobal('window', {}) + + httpSrv = http.createServer() + + // @ts-expect-error + server = new SocketIOServer(httpSrv, { path: '/__socket' }) + + // use an event emitter and wrap in in a vitest mock to assert on calls + const webSocketEventBus = new EventEmitter() + const ws = { + on: vi.fn().mockImplementation(webSocketEventBus.on), + emit: vi.fn(webSocketEventBus.emit), + } as unknown as SocketShape + + vi.mocked(connect).mockReturnValue(ws) + + browser.runtime.getBrowserInfo = vi.fn().mockResolvedValue({ name: 'Firefox' }) + + connectWrapper = async (options = {}) => { + const ws = automation.connect(`http://localhost:${PORT}`, '/__socket.io') + + // skip 'connect' and 'automation:client:connected' and trigger + // the handler that kicks everything off + ws.emit('automation:config', options) + + await new Promise((resolve) => setTimeout(resolve, 1)) + + return ws + } + + return new Promise((resolve) => { + httpSrv.listen(PORT, resolve) + }) + }) + + afterEach(function () { + server.close() + + return new Promise((resolve) => { + httpSrv.close(() => { + resolve() + }) + }) + }) + + describe('.connect', () => { + it('emits \'automation:client:connected\'', async function () { + const ws = automation.connect(`http://localhost:${PORT}`, '/__socket.io') + + ws.emit('connect') + + expect(ws.emit).toHaveBeenCalledWith('automation:client:connected') + }) + + it('listens to cookie changes', async function () { + await connectWrapper() + + expect(browser.cookies.onChanged.addListener).toHaveBeenCalledOnce() + }) + }) + + describe('cookies', () => { + it('onChanged does not emit when cause is overwrite', async function () { + const ws = await connectWrapper() + // @ts-expect-error + const fn = browser.cookies.onChanged.addListener.mock.calls[0][0] + + fn({ cause: 'overwrite' }) + + expect(ws.emit).not.toHaveBeenCalledWith('automation:push:request') + }) + + it('onChanged emits automation:push:request change:cookie', async function () { + const info = { cause: 'explicit', cookie: { name: 'foo', value: 'bar' } } + + vi.mocked(browser.cookies.onChanged.addListener).mockImplementation((fn) => fn(info as any)) + + const ws = await connectWrapper() + + expect(ws.emit).toHaveBeenCalledWith('automation:push:request', 'change:cookie', info) + }) + }) + + describe('downloads', () => { + it('onCreated emits automation:push:request create:download', async function () { + const downloadItem = { + id: '1', + filename: '/path/to/download.csv', + mime: 'text/csv', + url: 'http://localhost:1234/download.csv', + } + + vi.mocked(browser.downloads.onCreated.addListener).mockImplementation((fn) => fn(downloadItem as any)) + + const ws = await connectWrapper() + + expect(ws.emit).toHaveBeenCalledWith('automation:push:request', 'create:download', { + id: `${downloadItem.id}`, + filePath: downloadItem.filename, + mime: downloadItem.mime, + url: downloadItem.url, + }) + }) + + it('onChanged emits automation:push:request complete:download', async function () { + const downloadDelta = { + id: '1', + state: { + current: 'complete', + }, + } + + vi.mocked(browser.downloads.onChanged.addListener).mockImplementation((fn) => fn(downloadDelta as any)) + + const ws = await connectWrapper() + + expect(ws.emit).toHaveBeenCalledWith('automation:push:request', 'complete:download', { + id: `${downloadDelta.id}`, + }) + }) + + it('onChanged emits automation:push:request canceled:download', async function () { + const downloadDelta = { + id: '1', + state: { + current: 'canceled', + }, + } + + vi.mocked(browser.downloads.onChanged.addListener).mockImplementation((fn) => fn(downloadDelta as any)) + + const ws = await connectWrapper() + + expect(ws.emit).toHaveBeenCalledWith('automation:push:request', 'canceled:download', { + id: `${downloadDelta.id}`, + }) + }) + + it('onChanged does not emit if state does not exist', async function () { + const downloadDelta = { + id: '1', + } + + vi.mocked(browser.downloads.onChanged.addListener).mockImplementation((fn) => fn(downloadDelta as any)) + + const ws = await connectWrapper() + + expect(ws.emit).not.toHaveBeenCalledWith('automation:push:request') + }) + + it('onChanged does not emit if state.current is not "complete"', async function () { + const downloadDelta = { + id: '1', + state: { + current: 'inprogress', + }, + } + + vi.mocked(browser.downloads.onChanged.addListener).mockImplementation((fn) => fn(downloadDelta as any)) + + const ws = await connectWrapper() + + expect(ws.emit).not.toHaveBeenCalledWith('automation:push:request') + }) + + it('does not add downloads listener if in non-Firefox browser', async function () { + vi.mocked(browser.runtime.getBrowserInfo).mockResolvedValue({ name: 'Chrome' } as any) + + await connectWrapper() + + expect(browser.downloads.onCreated.addListener).not.toHaveBeenCalled() + expect(browser.downloads.onChanged.addListener).not.toHaveBeenCalled() + }) + }) + + describe('integration', () => { + let socket: SocketShape + + beforeEach(async function () { + const { connect: connectActual } = await vi.importActual('../../../app/v2/client') + + vi.mocked(connect).mockImplementation(connectActual) + + await new Promise((resolve) => { + server.on('connection', (socket1) => { + socket = socket1 as unknown as SocketShape + + resolve() + }) + + automation.connect(`http://localhost:${PORT}`, '/__socket') + }) + }) + + describe('reset:browser:state', () => { + beforeEach(() => { + vi.mocked(browser.browsingData.remove).mockImplementation((args: any, options: any) => { + if (_.isEqual(args, {}) && _.isEqual(options, { cache: true, cookies: true, downloads: true, formData: true, history: true, indexedDB: true, localStorage: true, passwords: true, pluginData: true, serviceWorkers: true })) { + return Promise.resolve() + } + + return Promise.reject(new Error('Unexpected arguments')) + }) + }) + + it('resets the browser state', function () { + return new Promise((resolve) => { + socket.on('automation:response', (id: number, obj: { response: unknown }) => { + expect(id).toEqual(123) + expect(obj.response).toBeUndefined() + + expect(browser.browsingData.remove).toHaveBeenCalled() + + resolve() + }) + + server.emit('automation:request', 123, 'reset:browser:state') + }) + }) + }) + }) +}) diff --git a/packages/extension/test/integration/v2/background_spec.js b/packages/extension/test/integration/v2/background_spec.js deleted file mode 100644 index 6eb2ce06b9..0000000000 --- a/packages/extension/test/integration/v2/background_spec.js +++ /dev/null @@ -1,244 +0,0 @@ -/* tslint:disable:no-empty */ -require('../../spec_helper') -const _ = require('lodash') -const http = require('http') -const { SocketIOServer } = require('@packages/socket') -const mockRequire = require('mock-require') -const client = require('../../../app/v2/client') - -const browser = { - cookies: { - onChanged: { - addListener () {}, - }, - }, - downloads: { - onCreated: { - addListener () {}, - }, - onChanged: { - addListener () {}, - }, - }, - runtime: {}, - browsingData: { - remove () {}, - }, -} - -mockRequire('webextension-polyfill', browser) - -const background = require('../../../app/v2/background') -const { expect } = require('chai') - -const PORT = 12345 - -describe('app/background', () => { - beforeEach(function (done) { - global.window = {} - - this.httpSrv = http.createServer() - this.server = new SocketIOServer(this.httpSrv, { path: '/__socket' }) - - const ws = { - on: sinon.stub(), - emit: sinon.stub(), - } - - sinon.stub(client, 'connect').returns(ws) - - browser.runtime.getBrowserInfo = sinon.stub().resolves({ name: 'Firefox' }), - - this.connect = async (options = {}) => { - const ws = background.connect(`http://localhost:${PORT}`, '/__socket.io') - - // skip 'connect' and 'automation:client:connected' and trigger - // the handler that kicks everything off - await ws.on.withArgs('automation:config').args[0][1](options) - - return ws - } - - this.httpSrv.listen(PORT, done) - }) - - afterEach(function (done) { - this.server.close() - - this.httpSrv.close(() => { - done() - }) - }) - - context('.connect', () => { - it('emits \'automation:client:connected\'', async function () { - const ws = background.connect(`http://localhost:${PORT}`, '/__socket.io') - - await ws.on.withArgs('connect').args[0][1]() - - expect(ws.emit).to.be.calledWith('automation:client:connected') - }) - - it('listens to cookie changes', async function () { - const addListener = sinon.stub(browser.cookies.onChanged, 'addListener') - - await this.connect() - - expect(addListener).to.be.calledOnce - }) - }) - - context('cookies', () => { - it('onChanged does not emit when cause is overwrite', async function () { - const addListener = sinon.stub(browser.cookies.onChanged, 'addListener') - const ws = await this.connect() - const fn = addListener.getCall(0).args[0] - - fn({ cause: 'overwrite' }) - - expect(ws.emit).not.to.be.calledWith('automation:push:request') - }) - - it('onChanged emits automation:push:request change:cookie', async function () { - const info = { cause: 'explicit', cookie: { name: 'foo', value: 'bar' } } - - sinon.stub(browser.cookies.onChanged, 'addListener').yields(info) - - const ws = await this.connect() - - expect(ws.emit).to.be.calledWith('automation:push:request', 'change:cookie', info) - }) - }) - - context('downloads', () => { - it('onCreated emits automation:push:request create:download', async function () { - const downloadItem = { - id: '1', - filename: '/path/to/download.csv', - mime: 'text/csv', - url: 'http://localhost:1234/download.csv', - } - - sinon.stub(browser.downloads.onCreated, 'addListener').yields(downloadItem) - - const ws = await this.connect() - - expect(ws.emit).to.be.calledWith('automation:push:request', 'create:download', { - id: `${downloadItem.id}`, - filePath: downloadItem.filename, - mime: downloadItem.mime, - url: downloadItem.url, - }) - }) - - it('onChanged emits automation:push:request complete:download', async function () { - const downloadDelta = { - id: '1', - state: { - current: 'complete', - }, - } - - sinon.stub(browser.downloads.onChanged, 'addListener').yields(downloadDelta) - - const ws = await this.connect() - - expect(ws.emit).to.be.calledWith('automation:push:request', 'complete:download', { - id: `${downloadDelta.id}`, - }) - }) - - it('onChanged emits automation:push:request canceled:download', async function () { - const downloadDelta = { - id: '1', - state: { - current: 'canceled', - }, - } - - sinon.stub(browser.downloads.onChanged, 'addListener').yields(downloadDelta) - - const ws = await this.connect() - - expect(ws.emit).to.be.calledWith('automation:push:request', 'canceled:download', { - id: `${downloadDelta.id}`, - }) - }) - - it('onChanged does not emit if state does not exist', async function () { - const downloadDelta = { - id: '1', - } - const addListener = sinon.stub(browser.downloads.onChanged, 'addListener') - - const ws = await this.connect() - - addListener.getCall(0).args[0](downloadDelta) - - expect(ws.emit).not.to.be.calledWith('automation:push:request') - }) - - it('onChanged does not emit if state.current is not "complete"', async function () { - const downloadDelta = { - id: '1', - state: { - current: 'inprogress', - }, - } - const addListener = sinon.stub(browser.downloads.onChanged, 'addListener') - - const ws = await this.connect() - - addListener.getCall(0).args[0](downloadDelta) - - expect(ws.emit).not.to.be.calledWith('automation:push:request') - }) - - it('does not add downloads listener if in non-Firefox browser', async function () { - browser.runtime.getBrowserInfo = undefined - - const onCreated = sinon.stub(browser.downloads.onCreated, 'addListener') - const onChanged = sinon.stub(browser.downloads.onChanged, 'addListener') - - await this.connect() - - expect(onCreated).not.to.be.called - expect(onChanged).not.to.be.called - }) - }) - - context('integration', () => { - beforeEach(function (done) { - done = _.once(done) - - client.connect.restore() - - this.server.on('connection', (socket1) => { - this.socket = socket1 - - done() - }) - - this.client = background.connect(`http://localhost:${PORT}`, '/__socket') - }) - - describe('reset:browser:state', () => { - beforeEach(() => { - sinon.stub(browser.browsingData, 'remove').withArgs({}, { cache: true, cookies: true, downloads: true, formData: true, history: true, indexedDB: true, localStorage: true, passwords: true, pluginData: true, serviceWorkers: true }).resolves() - }) - - it('resets the browser state', function (done) { - this.socket.on('automation:response', (id, obj) => { - expect(id).to.eq(123) - expect(obj.response).to.be.undefined - - expect(browser.browsingData.remove).to.be.called - - done() - }) - - this.server.emit('automation:request', 123, 'reset:browser:state') - }) - }) - }) -}) diff --git a/packages/extension/test/integration/v3/content.spec.ts b/packages/extension/test/integration/v3/content.spec.ts new file mode 100644 index 0000000000..2e758f59fa --- /dev/null +++ b/packages/extension/test/integration/v3/content.spec.ts @@ -0,0 +1,124 @@ +import { describe, expect, beforeAll, beforeEach, it, vi } from 'vitest' + +describe('app/v3/content', () => { + let port: { onMessage: { addListener: () => void }, postMessage: () => void } + let chrome: { runtime: { connect: () => { onMessage: { addListener: () => void } } } } + let window: { addEventListener: () => void, postMessage: () => void } + + beforeAll(async () => { + port = { + onMessage: { + addListener: vi.fn(), + }, + postMessage: vi.fn(), + } + + chrome = { + runtime: { + connect: vi.fn().mockReturnValue(port), + }, + } + + // @ts-expect-error + global.chrome = chrome + + window = { + addEventListener: vi.fn(), + postMessage: vi.fn(), + }, + + // @ts-expect-error + global.window = window + }) + + beforeEach(() => { + vi.resetModules() + vi.clearAllMocks() + }) + + it('adds window message listener and port onMessage listener', async () => { + await vi.importActual('../../../app/v3/content') + expect(window.addEventListener).toHaveBeenCalledWith('message', expect.any(Function)) + expect(port.onMessage.addListener).toHaveBeenCalledWith(expect.any(Function)) + }) + + describe('messages from window (i.e Cypress)', () => { + describe('on cypress:extension:activate:main:tab', () => { + const data = { message: 'cypress:extension:activate:main:tab' } + + it('posts message to port', async () => { + // @ts-expect-error + vi.mocked(window.addEventListener).mockImplementation((event: MessageEvent, callback: (event: MessageEvent) => void) => callback({ data, source: window } as any)) + + await vi.importActual('../../../app/v3/content') + + expect(port.postMessage).toHaveBeenCalledWith({ + message: 'activate:main:tab', + }) + }) + + it('is a noop if source is not the same window', async () => { + // @ts-expect-error + vi.mocked(window.addEventListener).mockImplementation((event: MessageEvent, callback: (event: MessageEvent) => void) => callback({ data, source: {} } as any)) + await vi.importActual('../../../app/v3/content') + + expect(port.postMessage).not.toHaveBeenCalled() + }) + }) + + describe('on cypress:extension:url:changed', () => { + const data = { message: 'cypress:extension:url:changed', url: 'the://url' } + + it('posts message to port', async () => { + // @ts-expect-error + vi.mocked(window.addEventListener).mockImplementation((event: MessageEvent, callback: (event: MessageEvent) => void) => callback({ data, source: window } as any)) + await vi.importActual('../../../app/v3/content') + + expect(port.postMessage).toHaveBeenCalledWith({ + message: 'url:changed', + url: data.url, + }) + }) + + it('is a noop if source is not the same window', async () => { + // @ts-expect-error + vi.mocked(window.addEventListener).mockImplementation((event: MessageEvent, callback: (event: MessageEvent) => void) => callback({ data, source: {} } as any)) + await vi.importActual('../../../app/v3/content') + + expect(port.postMessage).not.toHaveBeenCalled() + }) + }) + + it('is a noop if message is not supported', async () => { + const data = { message: 'unsupported' } + + // @ts-expect-error + vi.mocked(window.addEventListener).mockImplementation((event: MessageEvent, callback: (event: MessageEvent) => void) => callback({ data, source: window } as any)) + await vi.importActual('../../../app/v3/content') + + expect(port.postMessage).not.toHaveBeenCalled() + }) + }) + + describe('messages from port (i.e. service worker)', () => { + describe('on main:tab:activated', () => { + it('posts message to window', async () => { + // @ts-expect-error + vi.mocked(port.onMessage.addListener).mockImplementation((callback: (event: MessageEvent) => void) => callback({ message: 'main:tab:activated' } as any)) + await vi.importActual('../../../app/v3/content') + + expect(window.postMessage).toHaveBeenCalledWith({ message: 'cypress:extension:main:tab:activated' }, '*') + }) + }) + + it('is a noop if message is not main:tab:activated', async () => { + const data = { message: 'unsupported' } + + // @ts-expect-error + vi.mocked(port.onMessage.addListener).mockImplementation((callback: (event: MessageEvent) => void) => callback({ data, source: window } as any)) + await vi.importActual('../../../app/v3/content') + + expect(window.postMessage).not.toHaveBeenCalled() + }) + }) +}) diff --git a/packages/extension/test/integration/v3/content_spec.js b/packages/extension/test/integration/v3/content_spec.js deleted file mode 100644 index 8816a6c652..0000000000 --- a/packages/extension/test/integration/v3/content_spec.js +++ /dev/null @@ -1,108 +0,0 @@ -require('../../spec_helper') - -describe('app/v3/content', () => { - let port - let chrome - let window - - before(() => { - port = { - onMessage: { - addListener: sinon.stub(), - }, - postMessage: sinon.stub(), - } - - chrome = { - runtime: { - connect: sinon.stub().returns(port), - }, - } - - global.chrome = chrome - - window = { - addEventListener: sinon.stub(), - postMessage: sinon.stub(), - }, - - global.window = window - - require('../../../app/v3/content') - }) - - beforeEach(() => { - port.postMessage.reset() - window.postMessage.reset() - }) - - it('adds window message listener and port onMessage listener', () => { - expect(window.addEventListener).to.be.calledWith('message', sinon.match.func) - expect(port.onMessage.addListener).to.be.calledWith(sinon.match.func) - }) - - describe('messages from window (i.e Cypress)', () => { - describe('on cypress:extension:activate:main:tab', () => { - const data = { message: 'cypress:extension:activate:main:tab' } - - it('posts message to port', () => { - window.addEventListener.yield({ data, source: window }) - - expect(port.postMessage).to.be.calledWith({ - message: 'activate:main:tab', - }) - }) - - it('is a noop if source is not the same window', () => { - window.addEventListener.yield({ data, source: {} }) - - expect(port.postMessage).not.to.be.called - }) - }) - - describe('on cypress:extension:url:changed', () => { - const data = { message: 'cypress:extension:url:changed', url: 'the://url' } - - it('posts message to port', () => { - window.addEventListener.yield({ data, source: window }) - - expect(port.postMessage).to.be.calledWith({ - message: 'url:changed', - url: data.url, - }) - }) - - it('is a noop if source is not the same window', () => { - window.addEventListener.yield({ data, source: {} }) - - expect(port.postMessage).not.to.be.called - }) - }) - - it('is a noop if message is not supported', () => { - const data = { message: 'unsupported' } - - window.addEventListener.yield({ data, source: window }) - - expect(port.postMessage).not.to.be.called - }) - }) - - describe('messages from port (i.e. service worker)', () => { - describe('on main:tab:activated', () => { - it('posts message to window', () => { - port.onMessage.addListener.yield({ message: 'main:tab:activated' }) - - expect(window.postMessage).to.be.calledWith({ message: 'cypress:extension:main:tab:activated' }, '*') - }) - }) - - it('is a noop if message is not main:tab:activated', () => { - const data = { message: 'unsupported' } - - port.onMessage.addListener.yield({ data, source: window }) - - expect(window.postMessage).not.to.be.called - }) - }) -}) diff --git a/packages/extension/test/integration/v3/service-worker.spec.ts b/packages/extension/test/integration/v3/service-worker.spec.ts new file mode 100644 index 0000000000..e3a75afc4d --- /dev/null +++ b/packages/extension/test/integration/v3/service-worker.spec.ts @@ -0,0 +1,157 @@ +import { describe, it, expect, beforeAll, beforeEach, vi } from 'vitest' + +describe('app/v3/service-worker', () => { + let chrome: { runtime: { onConnect: { addListener: () => void } }, tabs: { query: () => void, update: () => void }, storage: { local: { set: () => void, get: () => void } } } + let port: { onMessage: { addListener: () => void }, postMessage: () => void } + + beforeAll(() => { + chrome = { + runtime: { + onConnect: { + addListener: vi.fn(), + }, + }, + tabs: { + query: vi.fn(), + update: vi.fn(), + }, + storage: { + local: { + set: vi.fn(), + get: vi.fn(), + }, + }, + } + + // @ts-expect-error + global.chrome = chrome + }) + + beforeEach(() => { + vi.resetModules() + vi.clearAllMocks() + + port = { + onMessage: { + addListener: vi.fn(), + }, + postMessage: vi.fn(), + } + }) + + it('adds onConnect listener', async () => { + await vi.importActual('../../../app/v3/service-worker') + expect(chrome.runtime.onConnect.addListener).toHaveBeenCalledWith(expect.any(Function)) + }) + + it('adds port onMessage listener', async () => { + // @ts-expect-error + vi.mocked(chrome.runtime.onConnect.addListener).mockImplementation((fn: (port: { onMessage: { addListener: () => void } }) => void) => fn(port)) + await vi.importActual('../../../app/v3/service-worker') + + expect(port.onMessage.addListener).toHaveBeenCalledWith(expect.any(Function)) + }) + + describe('on message', () => { + beforeEach(() => { + // @ts-expect-error + vi.mocked(chrome.runtime.onConnect.addListener).mockImplementation((fn: (port: { onMessage: { addListener: () => void } }) => void) => fn(port)) + }) + + describe('activate:main:tab', () => { + const tab1 = { id: '1', url: 'the://url' } + const tab2 = { id: '2', url: 'some://other.url' } + + beforeEach(() => { + // @ts-expect-error + vi.mocked(chrome.tabs.query).mockResolvedValue([tab1, tab2]) + }) + + describe('when there is a most recent url', () => { + beforeEach(() => { + // @ts-expect-error + vi.mocked(chrome.storage.local.get).mockImplementation((key: string, callback: (result: { mostRecentUrl: string }) => void) => callback({ mostRecentUrl: tab1.url })) + }) + + it('activates the tab matching the url', async () => { + // @ts-expect-error + vi.mocked(port.onMessage.addListener).mockImplementation((callback: (event: MessageEvent) => void) => callback({ message: 'activate:main:tab' } as any)) + + await vi.importActual('../../../app/v3/service-worker') + + expect(chrome.tabs.update).toHaveBeenCalledWith(tab1.id, { active: true }) + }) + + describe('but no tab matches the most recent url', () => { + beforeEach(() => { + // @ts-expect-error + vi.mocked(chrome.tabs.query).mockResolvedValue([tab2]) + }) + + it('does not try to activate any tabs', async () => { + // @ts-expect-error + vi.mocked(port.onMessage.addListener).mockImplementation((callback: (event: MessageEvent) => void) => callback({ message: 'activate:main:tab' } as any)) + await vi.importActual('../../../app/v3/service-worker') + + expect(chrome.tabs.update).not.toHaveBeenCalled() + }) + }) + + describe('and chrome throws an error while activating the tab', () => { + let err: Error + + beforeEach(() => { + vi.spyOn(console, 'log').mockImplementation(() => undefined) + err = new Error('uh oh') + vi.mocked(chrome.tabs.update).mockRejectedValue(err) + }) + + it('is a noop, logging the error', async () => { + // @ts-expect-error + vi.mocked(port.onMessage.addListener).mockImplementation((callback: (event: MessageEvent) => void) => callback({ message: 'activate:main:tab' } as any)) + await vi.importActual('../../../app/v3/service-worker') + + // eslint-disable-next-line no-console + expect(console.log).toHaveBeenCalledWith('Activating main Cypress tab errored:', err) + }) + }) + }) + + describe('when there is not a most recent url', () => { + beforeEach(() => { + // @ts-expect-error + vi.mocked(chrome.storage.local.get).mockImplementation((key: string, callback: (result: { mostRecentUrl: string }) => void) => callback({})) + }) + + it('does not try to activate any tabs', async () => { + // @ts-expect-error + vi.mocked(port.onMessage.addListener).mockImplementation((callback: (event: MessageEvent) => void) => callback({ message: 'activate:main:tab' } as any)) + await vi.importActual('../../../app/v3/service-worker') + + expect(chrome.tabs.update).not.toHaveBeenCalled() + }) + }) + }) + + describe('url:changed', () => { + it('sets the mostRecentUrl', async () => { + const url = 'some://url' + + // @ts-expect-error + vi.mocked(port.onMessage.addListener).mockImplementation((callback: (event: MessageEvent) => void) => callback({ message: 'url:changed', url } as any)) + await vi.importActual('../../../app/v3/service-worker') + + expect(chrome.storage.local.set).toHaveBeenCalledWith({ mostRecentUrl: url }) + }) + }) + + it('is a noop if message is not a supported message', async () => { + // @ts-expect-error + vi.mocked(port.onMessage.addListener).mockImplementation((callback: (event: MessageEvent) => void) => callback({ message: 'unsupported' } as any)) + await vi.importActual('../../../app/v3/service-worker') + + expect(chrome.tabs.update).not.toHaveBeenCalled() + expect(chrome.storage.local.set).not.toHaveBeenCalled() + }) + }) +}) diff --git a/packages/extension/test/integration/v3/service-worker_spec.js b/packages/extension/test/integration/v3/service-worker_spec.js deleted file mode 100644 index 3591f4c0e8..0000000000 --- a/packages/extension/test/integration/v3/service-worker_spec.js +++ /dev/null @@ -1,137 +0,0 @@ -require('../../spec_helper') - -describe('app/v3/service-worker', () => { - let chrome - let port - - before(() => { - chrome = { - runtime: { - onConnect: { - addListener: sinon.stub(), - }, - }, - tabs: { - query: sinon.stub(), - update: sinon.stub(), - }, - storage: { - local: { - set: sinon.stub(), - get: sinon.stub(), - }, - }, - } - - global.chrome = chrome - - require('../../../app/v3/service-worker') - }) - - beforeEach(() => { - chrome.tabs.query.reset() - chrome.tabs.update.reset() - chrome.storage.local.set.reset() - chrome.storage.local.get.reset() - - port = { - onMessage: { - addListener: sinon.stub(), - }, - postMessage: sinon.stub(), - } - }) - - it('adds onConnect listener', () => { - expect(chrome.runtime.onConnect.addListener).to.be.calledWith(sinon.match.func) - }) - - it('adds port onMessage listener', () => { - chrome.runtime.onConnect.addListener.yield(port) - - expect(port.onMessage.addListener).to.be.calledWith(sinon.match.func) - }) - - describe('on message', () => { - beforeEach(() => { - chrome.runtime.onConnect.addListener.yield(port) - }) - - describe('activate:main:tab', () => { - const tab1 = { id: '1', url: 'the://url' } - const tab2 = { id: '2', url: 'some://other.url' } - - beforeEach(() => { - chrome.tabs.query.resolves([tab1, tab2]) - }) - - describe('when there is a most recent url', () => { - beforeEach(() => { - chrome.storage.local.get.callsArgWith(1, { mostRecentUrl: tab1.url }) - }) - - it('activates the tab matching the url', async () => { - await port.onMessage.addListener.yield({ message: 'activate:main:tab' })[0] - - expect(chrome.tabs.update).to.be.calledWith(tab1.id, { active: true }) - }) - - describe('but no tab matches the most recent url', () => { - beforeEach(() => { - chrome.tabs.query.reset() - chrome.tabs.query.resolves([tab2]) - }) - - it('does not try to activate any tabs', async () => { - await port.onMessage.addListener.yield({ message: 'activate:main:tab' })[0] - expect(chrome.tabs.update).not.to.be.called - }) - }) - - describe('and chrome throws an error while activating the tab', () => { - let err - - beforeEach(() => { - sinon.stub(console, 'log') - err = new Error('uh oh') - chrome.tabs.update.rejects(err) - }) - - it('is a noop, logging the error', async () => { - await port.onMessage.addListener.yield({ message: 'activate:main:tab' })[0] - - // eslint-disable-next-line no-console - expect(console.log).to.be.calledWith('Activating main Cypress tab errored:', err) - }) - }) - }) - - describe('when there is not a most recent url', () => { - beforeEach(() => { - chrome.storage.local.get.callsArgWith(1, {}) - }) - - it('does not try to activate any tabs', async () => { - await port.onMessage.addListener.yield({ message: 'activate:main:tab' })[0] - expect(chrome.tabs.update).not.to.be.called - }) - }) - }) - - describe('url:changed', () => { - it('sets the mostRecentUrl', async () => { - const url = 'some://url' - - await port.onMessage.addListener.yield({ message: 'url:changed', url })[0] - expect(chrome.storage.local.set).to.be.calledWith({ mostRecentUrl: url }) - }) - }) - - it('is a noop if message is not a supported message', async () => { - await port.onMessage.addListener.yield({ message: 'unsupported' })[0] - - expect(chrome.tabs.update).not.to.be.called - expect(chrome.storage.local.set).not.to.be.called - }) - }) -}) diff --git a/packages/extension/test/mocha.opts b/packages/extension/test/mocha.opts deleted file mode 100644 index 831ae85fda..0000000000 --- a/packages/extension/test/mocha.opts +++ /dev/null @@ -1,4 +0,0 @@ -test/unit -test/integration ---reporter spec ---recursive diff --git a/packages/extension/test/spec_helper.js b/packages/extension/test/spec_helper.js deleted file mode 100644 index 020b86c513..0000000000 --- a/packages/extension/test/spec_helper.js +++ /dev/null @@ -1,12 +0,0 @@ -const chai = require('chai') -const sinon = require('sinon') -const sinonChai = require('sinon-chai') - -chai.use(sinonChai) - -global.sinon = sinon -global.expect = chai.expect - -afterEach(() => { - return sinon.restore() -}) diff --git a/packages/extension/test/unit/extension.spec.ts b/packages/extension/test/unit/extension.spec.ts new file mode 100644 index 0000000000..8142428924 --- /dev/null +++ b/packages/extension/test/unit/extension.spec.ts @@ -0,0 +1,111 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest' +import { exec } from 'child_process' +import fs from 'fs-extra' +import path from 'path' +import * as extension from '../../lib/index' + +vi.mock('../../lib/index', async (importActual) => { + const actual = await importActual() + + return { + // @ts-expect-error + ...actual, + getPathToExtension: vi.fn(), + } +}) + +const cwd = process.cwd() + +describe('Extension', () => { + describe('.getPathToExtension', () => { + beforeEach(async () => { + const { getPathToExtension } = await vi.importActual('../../lib/index') + + // use the actual implementation for these tests + vi.mocked(extension.getPathToExtension).mockImplementation(getPathToExtension) + }) + + it('returns path to app-dist/v2', () => { + const result = extension.getPathToExtension() + const expected = path.join(cwd, 'app-dist', 'v2') + + expect(path.normalize(result)).toEqual(path.normalize(expected)) + }) + + it('returns path to files in app-dist/v2', () => { + const result = extension.getPathToExtension('background.js') + const expected = path.join(cwd, '/app-dist/v2/background.js') + + expect(path.normalize(result)).toEqual(path.normalize(expected)) + }) + }) + + describe('.getPathToV3Extension', () => { + it('returns path to app-dist/v3', () => { + const result = extension.getPathToV3Extension() + const expected = path.join(cwd, 'app-dist', 'v3') + + expect(path.normalize(result)).toEqual(path.normalize(expected)) + }) + }) + + describe('.getPathToTheme', () => { + it('returns path to theme', () => { + const result = extension.getPathToTheme() + const expected = path.join(cwd, 'theme') + + expect(path.normalize(result)).toEqual(path.normalize(expected)) + }) + }) + + describe('.getPathToRoot', () => { + it('returns path to root', () => { + expect(extension.getPathToRoot()).toEqual(cwd) + }) + }) + + describe('.setHostAndPath', () => { + let src: string + + beforeEach(function () { + src = path.join(cwd, 'test', 'helpers', 'background.js') + + vi.mocked(extension.getPathToExtension).mockImplementation((file) => { + if (file === 'background.js') { + return src + } + + throw new Error(`Unexpected file: ${file}`) + }) + }) + + it('does not mutate background.js', async () => { + const str = await fs.readFile(src, 'utf8') + + await extension.setHostAndPath('http://dev.local:8080', '/__foo') + + const str2 = await fs.readFile(src, 'utf8') + + expect(str).toEqual(str2) + }) + }) + + describe('manifest', () => { + it('has a key that resolves to the static extension ID', async () => { + const manifest = await fs.readJson(path.join(cwd, 'app/v2/manifest.json')) + const cmd = `echo \"${manifest.key}\" | openssl base64 -d -A | shasum -a 256 | head -c32 | tr 0-9a-f a-p` + + const stdout = await new Promise((resolve, reject) => { + exec(cmd, (error, stdout) => { + if (error) { + reject(error) + } + + resolve(stdout) + }) + }) + + expect(stdout).toEqual('caljajdfkjjjdehjdoimjkkakekklcck') + }) + }) +}) diff --git a/packages/extension/test/unit/extension_spec.js b/packages/extension/test/unit/extension_spec.js deleted file mode 100644 index 3f3d518f1c..0000000000 --- a/packages/extension/test/unit/extension_spec.js +++ /dev/null @@ -1,136 +0,0 @@ -require('../spec_helper') - -let { exec } = require('child_process') -let fs = require('fs-extra') -const eol = require('eol') -const path = require('path') -const Promise = require('bluebird') -const extension = require('../../index') - -const cwd = process.cwd() - -fs = Promise.promisifyAll(fs) -exec = Promise.promisify(exec) - -describe('Extension', () => { - context('.getCookieUrl', () => { - it('returns cookie url', () => { - expect(extension.getCookieUrl({ - name: 'foo', - value: 'bar', - path: '/foo/bar', - domain: 'www.google.com', - secure: true, - })).to.eq('https://www.google.com/foo/bar') - }) - }) - - context('.getPathToExtension', () => { - it('returns path to dist/v2', () => { - const result = extension.getPathToExtension() - const expected = path.join(cwd, 'dist', 'v2') - - expect(path.normalize(result)).to.eq(path.normalize(expected)) - }) - - it('returns path to files in dist/v2', () => { - const result = extension.getPathToExtension('background.js') - const expected = path.join(cwd, '/dist/v2/background.js') - - expect(path.normalize(result)).to.eq(path.normalize(expected)) - }) - }) - - context('.getPathToV3Extension', () => { - it('returns path to dist/v3', () => { - const result = extension.getPathToV3Extension() - const expected = path.join(cwd, 'dist', 'v3') - - expect(path.normalize(result)).to.eq(path.normalize(expected)) - }) - }) - - context('.getPathToTheme', () => { - it('returns path to theme', () => { - const result = extension.getPathToTheme() - const expected = path.join(cwd, 'theme') - - expect(path.normalize(result)).to.eq(path.normalize(expected)) - }) - }) - - context('.getPathToRoot', () => { - it('returns path to root', () => { - expect(extension.getPathToRoot()).to.eq(cwd) - }) - }) - - context('.setHostAndPath', () => { - beforeEach(function () { - this.src = path.join(cwd, 'test', 'helpers', 'background.js') - - return sinon.stub(extension, 'getPathToExtension') - .withArgs('background.js').returns(this.src) - }) - - it('rewrites the background.js source', () => { - return extension.setHostAndPath('http://dev.local:8080', '/__foo') - .then((str) => { - const result = eol.auto(str) - const expected = eol.auto(`\ -(function() { - var HOST, PATH, automation, client, fail, invoke, - slice = [].slice; - - HOST = "http://dev.local:8080"; - - PATH = "/__foo"; - - client = io.connect(HOST, { - path: PATH - }); - - automation = { - getAllCookies: function(filter, fn) { - if (filter == null) { - filter = {}; - } - return chrome.cookies.getAll(filter, fn); - } - }; - -}).call(this); -\ -`) - - expect(result).to.eq(expected) - }) - }) - - it('does not mutate background.js', function () { - return fs.readFileAsync(this.src, 'utf8') - .then((str) => { - return extension.setHostAndPath('http://dev.local:8080', '/__foo') - .then(() => { - return fs.readFileAsync(this.src, 'utf8') - }).then((str2) => { - expect(str).to.eq(str2) - }) - }) - }) - }) - - context('manifest', () => { - it('has a key that resolves to the static extension ID', () => { - return fs.readJsonAsync(path.join(cwd, 'app/v2/manifest.json')) - .then((manifest) => { - const cmd = `echo \"${manifest.key}\" | openssl base64 -d -A | shasum -a 256 | head -c32 | tr 0-9a-f a-p` - - return exec(cmd) - .then((stdout) => { - expect(stdout).to.eq('caljajdfkjjjdehjdoimjkkakekklcck') - }) - }) - }) - }) -}) diff --git a/packages/extension/tsconfig.app.v2.json b/packages/extension/tsconfig.app.v2.json new file mode 100644 index 0000000000..3fb598fc18 --- /dev/null +++ b/packages/extension/tsconfig.app.v2.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "app/v2/**/*.ts" + ], + "compilerOptions": { + "rootDir": "./app/v2", + "outDir": "./app-dist/v2", + "target": "ES2022", + "module": "Es2022" + } +} \ No newline at end of file diff --git a/packages/extension/tsconfig.app.v3.json b/packages/extension/tsconfig.app.v3.json new file mode 100644 index 0000000000..f1f908d5b2 --- /dev/null +++ b/packages/extension/tsconfig.app.v3.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "app/v3**/*.ts" + ], + "compilerOptions": { + "rootDir": "./app/v3", + "outDir": "./app-dist/v3", + "target": "ES2022", + "module": "ES2022" + } +} \ No newline at end of file diff --git a/packages/extension/tsconfig.json b/packages/extension/tsconfig.json index 37184bd1e8..ea5dab675c 100644 --- a/packages/extension/tsconfig.json +++ b/packages/extension/tsconfig.json @@ -1,8 +1,14 @@ { - "extends": "../ts/tsconfig.json", "compilerOptions": { - "target": "es2020", - "allowJs": true, - "strict": false + "moduleResolution": "node", + "strict": true, + "noImplicitAny": true, + "esModuleInterop": true, + "noUncheckedIndexedAccess": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "types": [ + "node" + ] } -} +} \ No newline at end of file diff --git a/packages/extension/tsconfig.lib.json b/packages/extension/tsconfig.lib.json new file mode 100644 index 0000000000..bb8a49c0c2 --- /dev/null +++ b/packages/extension/tsconfig.lib.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "lib/**/*.ts" + ], + "compilerOptions": { + "declaration": true, + "rootDir": "./lib", + "outDir": "./lib-dist", + "target": "ES2022", + "module": "CommonJS" + } +} \ No newline at end of file diff --git a/packages/extension/vitest.config.ts b/packages/extension/vitest.config.ts new file mode 100644 index 0000000000..1a9a321880 --- /dev/null +++ b/packages/extension/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + include: ['test/**/*.spec.ts'], + globals: true, + environment: 'node', + }, +}) diff --git a/packages/extension/webpack.config.js b/packages/extension/webpack.config.mjs similarity index 69% rename from packages/extension/webpack.config.js rename to packages/extension/webpack.config.mjs index 7dc314f78e..9d9002832a 100644 --- a/packages/extension/webpack.config.js +++ b/packages/extension/webpack.config.mjs @@ -1,9 +1,9 @@ -const path = require('path') -const webpack = require('webpack') +import path from 'path' +import webpack from 'webpack' -module.exports = { +export default { mode: process.env.NODE_ENV || 'development', - entry: './app/v2/init.js', + entry: './app/v2/init.ts', // https://github.com/cypress-io/cypress/issues/15032 // Default webpack output setting is "eval". // Chrome doesn't allow "eval" inside extensions. @@ -12,7 +12,14 @@ module.exports = { rules: [ { test: /\.tsx?$/, - use: 'ts-loader', + use: [ + { + loader: 'ts-loader', + options: { + configFile: path.resolve(import.meta.dirname, 'tsconfig.app.v2.json'), + }, + }, + ], exclude: /node_modules/, }, ], @@ -22,7 +29,7 @@ module.exports = { }, output: { filename: 'background.js', - path: path.resolve(__dirname, 'dist', 'v2'), + path: path.resolve(import.meta.dirname, 'app-dist', 'v2'), }, plugins: [ new webpack.DefinePlugin({ diff --git a/packages/proxy/lib/http/error-middleware.ts b/packages/proxy/lib/http/error-middleware.ts index 5331bd0f83..2610efb8d6 100644 --- a/packages/proxy/lib/http/error-middleware.ts +++ b/packages/proxy/lib/http/error-middleware.ts @@ -1,4 +1,4 @@ -import * as errors from '@packages/server/lib/errors' +import errors from '@packages/errors' import type { HttpMiddleware } from '.' import type { Readable } from 'stream' diff --git a/packages/server/lib/automation/cookies.ts b/packages/server/lib/automation/cookies.ts index ffd07b2144..3814323aeb 100644 --- a/packages/server/lib/automation/cookies.ts +++ b/packages/server/lib/automation/cookies.ts @@ -1,6 +1,5 @@ import _ from 'lodash' import Debug from 'debug' -import extension from '@packages/extension' import { isHostOnlyCookie } from '../browsers/cdp_automation' import type { SerializableAutomationCookie } from '../util/cookies' @@ -32,6 +31,19 @@ const normalizeCookies = (cookies: (SerializableAutomationCookie | AutomationCoo return _.map(cookies, normalizeCookieProps) as AutomationCookie[] } +const getCookieUrl = (cookie: { + secure?: boolean | null + domain?: string | null + path?: string | null +} = {}) => { + const prefix = cookie.secure ? 'https://' : 'http://' + + // https://github.com/cypress-io/cypress/issues/6375 + const host = cookie.domain?.startsWith('.') ? cookie.domain.slice(1) : cookie.domain + + return prefix + host + (cookie.path || '') +} + const normalizeCookieProps = function (automationCookie: SerializableAutomationCookie | AutomationCookie | null) { if (!automationCookie) { return automationCookie @@ -151,7 +163,7 @@ export class Cookies { // lets construct the url ourselves right now // unless we already have a URL - cookie.url = data.url != null ? data.url : extension.getCookieUrl(data) + cookie.url = data.url != null ? data.url : getCookieUrl(data) debug('set:cookie %o', cookie) @@ -175,7 +187,7 @@ export class Cookies { // lets construct the url ourselves right now // unless we already have a URL - cookie.url = data.url != null ? data.url : extension.getCookieUrl(data) + cookie.url = data.url != null ? data.url : getCookieUrl(data) return cookie }) diff --git a/packages/server/lib/browsers/chrome.ts b/packages/server/lib/browsers/chrome.ts index eaeb5069b5..a0d3e1ed37 100644 --- a/packages/server/lib/browsers/chrome.ts +++ b/packages/server/lib/browsers/chrome.ts @@ -4,7 +4,7 @@ import la from 'lazy-ass' import _ from 'lodash' import os from 'os' import path from 'path' -import extension from '@packages/extension' +import * as extension from '@packages/extension' import mime from 'mime' import { launch } from '@packages/launcher' diff --git a/packages/server/test/unit/browsers/firefox_spec.ts b/packages/server/test/unit/browsers/firefox_spec.ts index 49f8ca5faa..23f30f742a 100644 --- a/packages/server/test/unit/browsers/firefox_spec.ts +++ b/packages/server/test/unit/browsers/firefox_spec.ts @@ -337,7 +337,7 @@ describe('lib/browsers/firefox', () => { it('writes extension and ensure write access', async function () { mockfs({ - [path.resolve(`${__dirname }../../../../../extension/dist/v2`)]: { + [path.resolve(`${__dirname }../../../../../extension/app-dist/v2`)]: { 'background.js': mockfs.file({ mode: 0o444, }), diff --git a/yarn.lock b/yarn.lock index c8218e8138..44a87b0419 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3047,6 +3047,11 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== +"@discoveryjs/json-ext@^0.6.1": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz#f13c7c205915eb91ae54c557f5e92bddd8be0e83" + integrity sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ== + "@electron/asar@^3.2.13", "@electron/asar@^3.2.7": version "3.4.1" resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.4.1.tgz#4e9196a4b54fba18c56cd8d5cac67c5bdc588065" @@ -9017,6 +9022,11 @@ resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52" integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI= +"@types/webextension-polyfill@0.12.4": + version "0.12.4" + resolved "https://registry.yarnpkg.com/@types/webextension-polyfill/-/webextension-polyfill-0.12.4.tgz#d111b76e1ebf421fb64244598453bf44763a0266" + integrity sha512-wK8YdSI0pDiaehSLDIvtvonYmLwUUivg4Z6JCJO8rkyssMAG82cFJgwPK/V7NO61mJBLg/tXeoXQL8AFzpXZmQ== + "@types/webpack-bundle-analyzer@4.7.0": version "4.7.0" resolved "https://registry.npmjs.org/@types/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.7.0.tgz#fe199e724ce3d38705f6f1ba4d62429b7c360541" @@ -10166,16 +10176,31 @@ resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.1.1.tgz#3b2f852e91dac6e3b85fb2a314fb8bef46d94646" integrity sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw== +"@webpack-cli/configtest@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-3.0.1.tgz#76ac285b9658fa642ce238c276264589aa2b6b57" + integrity sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA== + "@webpack-cli/info@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.2.tgz#cc3fbf22efeb88ff62310cf885c5b09f44ae0fdd" integrity sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A== +"@webpack-cli/info@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-3.0.1.tgz#3cff37fabb7d4ecaab6a8a4757d3826cf5888c63" + integrity sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ== + "@webpack-cli/serve@^2.0.5": version "2.0.5" resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e" integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ== +"@webpack-cli/serve@^3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-3.0.1.tgz#bd8b1f824d57e30faa19eb78e4c0951056f72f00" + integrity sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg== + "@xmldom/xmldom@^0.8.8": version "0.8.10" resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99" @@ -13400,6 +13425,11 @@ commander@^10.0.0, commander@^10.0.1: resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== +commander@^12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + commander@^14.0.0: version "14.0.0" resolved "https://registry.yarnpkg.com/commander/-/commander-14.0.0.tgz#f244fc74a92343514e56229f16ef5c5e22ced5e9" @@ -15691,11 +15721,16 @@ env-paths@^3.0.0: resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-3.0.0.tgz#2f1e89c2f6dbd3408e1b1711dd82d62e317f58da" integrity sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A== -envinfo@7.13.0, envinfo@^7.7.3: +envinfo@7.13.0: version "7.13.0" resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.13.0.tgz#81fbb81e5da35d74e814941aeab7c325a606fb31" integrity sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q== +envinfo@^7.14.0, envinfo@^7.7.3: + version "7.18.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.18.0.tgz#38793d9dab9a5dec7b2a3146ed094cda8e754ed8" + integrity sha512-02QGCLRW+Jb8PC270ic02lat+N57iBaWsvHjcJViqp6UVupRB+Vsg7brYPTqEFXvsdTql3KnSczv5ModZFpl8Q== + environment@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" @@ -15727,11 +15762,6 @@ enzyme-adapter-utils@^1.11.0: prop-types "^15.7.2" semver "^5.7.1" -eol@0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/eol/-/eol-0.10.0.tgz#51b35c6b9aa0329a26d102b6ddc454be8654739b" - integrity sha512-+w3ktYrOphcIqC1XKmhQYvM+o2uxgQFiimL7B6JPZJlWVxf7Lno9e/JWLPIgbHo7DoZ+b7jsf/NzrUcNe6ZTZQ== - err-code@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" @@ -33298,6 +33328,25 @@ webpack-cli@^5.1.4: rechoir "^0.8.0" webpack-merge "^5.7.3" +webpack-cli@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-6.0.1.tgz#a1ce25da5ba077151afd73adfa12e208e5089207" + integrity sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw== + dependencies: + "@discoveryjs/json-ext" "^0.6.1" + "@webpack-cli/configtest" "^3.0.1" + "@webpack-cli/info" "^3.0.1" + "@webpack-cli/serve" "^3.0.1" + colorette "^2.0.14" + commander "^12.1.0" + cross-spawn "^7.0.3" + envinfo "^7.14.0" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^3.1.1" + rechoir "^0.8.0" + webpack-merge "^6.0.1" + webpack-dev-middleware@^7.4.2: version "7.4.2" resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz#40e265a3d3d26795585cff8207630d3a8ff05877" @@ -33362,6 +33411,15 @@ webpack-merge@^5.4.0, webpack-merge@^5.7.3: clone-deep "^4.0.1" wildcard "^2.0.0" +webpack-merge@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-6.0.1.tgz#50c776868e080574725abc5869bd6e4ef0a16c6a" + integrity sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.1" + webpack-sources@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" @@ -33624,10 +33682,10 @@ widest-line@^4.0.1: dependencies: string-width "^5.0.1" -wildcard@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" - integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== +wildcard@^2.0.0, wildcard@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== win-version-info@^6.0.1: version "6.0.1" From 2408fe5e96985ffd62d43af89f7bb904314298e6 Mon Sep 17 00:00:00 2001 From: Bill Glesias Date: Thu, 23 Oct 2025 10:35:29 -0400 Subject: [PATCH 3/3] chore: update expected result count --- .circleci/src/pipeline/@pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/src/pipeline/@pipeline.yml b/.circleci/src/pipeline/@pipeline.yml index 8a8125567c..f9c6abf37d 100644 --- a/.circleci/src/pipeline/@pipeline.yml +++ b/.circleci/src/pipeline/@pipeline.yml @@ -1785,7 +1785,7 @@ jobs: source ./scripts/ensure-node.sh yarn lerna run types - sanitize-verify-and-store-mocha-results: - expectedResultCount: 4 + expectedResultCount: 3 verify-release-readiness: <<: *defaults