Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/src/pipeline/@pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions guides/esm-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down Expand Up @@ -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**
Expand Down
2 changes: 2 additions & 0 deletions packages/extension/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
app-dist/
lib-dist/
8 changes: 8 additions & 0 deletions packages/extension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down
Original file line number Diff line number Diff line change
@@ -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')) {
Expand All @@ -15,17 +15,17 @@ 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)
}
})
})

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,
Expand All @@ -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') {
Expand All @@ -51,29 +51,30 @@ 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,
__name: err.name,
})
}

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)
Expand All @@ -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()
Expand All @@ -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
2 changes: 1 addition & 1 deletion packages/extension/app/v2/client.ts
Original file line number Diff line number Diff line change
@@ -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'],
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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])
})
})
Expand All @@ -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

Expand All @@ -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()

Expand Down
83 changes: 36 additions & 47 deletions packages/extension/gulpfile.ts
Original file line number Diff line number Diff line change
@@ -1,98 +1,87 @@
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<boolean> {
const removedAppDist = await rimraf('app-dist')
const removedLibDist = await rimraf('lib-dist')

return require('@packages/icons')
}

function clean (): Promise<boolean> {
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,
),
)

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)
5 changes: 0 additions & 5 deletions packages/extension/index.d.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/extension/index.js

This file was deleted.

Loading