Skip to content

Commit

Permalink
feat: new watcher and better sw
Browse files Browse the repository at this point in the history
- the new watcher actually works and keep the watched files updated on every rerun
- sw lifecicle improved to actually make sure its active before the tests run
- debug mode manual refresh recompiles everything similar to watch mode

BREAKING CHANGE: shouldn't break anything but a lot changed internally so to be same review your tests!
  • Loading branch information
hugomrdias committed Jul 13, 2021
1 parent db3709c commit fbfd613
Show file tree
Hide file tree
Showing 13 changed files with 126 additions and 90 deletions.
7 changes: 4 additions & 3 deletions mocks/sw/sw-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
// eslint-disable-next-line strict
const assert = require('assert')

const { KV } = require('./sw-globals')

describe('sw', () => {
it('should intercept and return kv and env"', async () => {
it('should intercept and return kv and env', async () => {
const out = await fetch('/favicon.ico')
const data = await out.json()

assert.strictEqual(data.env.browser, 'chromium')
assert.strictEqual(data.kv.test, 'value')
assert.strictEqual(data.kv.test, KV.test)
})
})
File renamed without changes.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"dependencies": {
"buffer": "^6.0.3",
"camelcase": "^6.2.0",
"chokidar": "^3.5.2",
"esbuild": "0.12.15",
"events": "^3.3.0",
"globby": "^11.0.4",
Expand Down
2 changes: 1 addition & 1 deletion src/runner-benchmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class BenchmarkRunner extends Runner {
* Compile tests
*
* @param {"before" | "bundle" | "watch"} mode
* @returns {Promise<string>} file to be loaded in the page
* @returns {Promise<import('./types.js').CompilerOutput>} file to be loaded in the page
*/
compiler(mode) {
/** @type {import('esbuild').Plugin} */
Expand Down
8 changes: 4 additions & 4 deletions src/runner-mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,9 @@ class MochaRunner extends Runner {

/**
* @param {import("playwright-core").Page} page
* @param {string} file
*/
async runTests(page, file) {
await super.runTests(page, file)
async runTests(page) {
const { outName, files } = await super.runTests(page)
switch (this.options.mode) {
case 'main': {
await page.evaluate(runMocha())
Expand All @@ -70,13 +69,14 @@ class MochaRunner extends Runner {
default:
throw new Error('mode not supported')
}
return { outName, files }
}

/**
* Compile tests
*
* @param {"before" | "bundle" | "watch"} mode
* @returns {Promise<string>} file to be loaded in the page
* @returns {Promise<import('./types.js').CompilerOutput>} file to be loaded in the page
*/
compiler(mode = 'bundle') {
/** @type {EsbuildPlugin} */
Expand Down
2 changes: 1 addition & 1 deletion src/runner-tape.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class TapeRunner extends Runner {
* Compile tests
*
* @param {"before" | "bundle" | "watch"} mode
* @returns {Promise<string>} file to be loaded in the page
* @returns {Promise<import('./types.js').CompilerOutput>} file to be loaded in the page
*/
compiler(mode = 'bundle') {
/** @type {EsbuildPlugin} */
Expand Down
7 changes: 3 additions & 4 deletions src/runner-uvu.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ self.PW_TEST.end(${pass})
class UvuRunner extends Runner {
/**
* @param {import("playwright-core").Page} page
* @param {string} file
*/
async runTests(page, file) {
async runTests(page) {
let total = 0
let passed = 0

Expand All @@ -29,14 +28,14 @@ class UvuRunner extends Runner {
}
})

await super.runTests(page, file)
return await super.runTests(page)
}

/**
* Compile tests
*
* @param {"before" | "bundle" | "watch"} mode
* @returns {Promise<string>} file to be loaded in the page
* @returns {Promise<import('./types.js').CompilerOutput>} file to be loaded in the page
*/
compiler(mode = 'bundle') {
return build(this, {}, '', mode)
Expand Down
8 changes: 4 additions & 4 deletions src/runner-zora.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ zora
class ZoraRunner extends Runner {
/**
* @param {import("playwright-core").Page} page
* @param {string} file
*/
async runTests(page, file) {
await super.runTests(page, file)
async runTests(page) {
const { outName, files } = await super.runTests(page)
switch (this.options.mode) {
case 'main': {
await page.evaluate(runZora())
Expand All @@ -49,13 +48,14 @@ class ZoraRunner extends Runner {
default:
throw new Error('mode not supported')
}
return { outName, files }
}

/**
* Compile tests
*
* @param {"before" | "bundle" | "watch"} mode
* @returns {Promise<string>} file to be loaded in the page
* @returns {Promise<import('./types.js').CompilerOutput>} file to be loaded in the page
*/
compiler(mode = 'bundle') {
/**
Expand Down
81 changes: 47 additions & 34 deletions src/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import { compileSw } from './utils/build-sw.js'
import mergeOptions from 'merge-options'
import { fileURLToPath } from 'node:url'
import { watch } from 'chokidar'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const merge = mergeOptions.bind({ ignoreUndefined: true })
Expand Down Expand Up @@ -202,29 +203,43 @@ export class Runner {
}

this.page.on('console', redirectConsole)
// uncaught rejections
this.page.on('pageerror', (err) => {
console.error(err)
this.stop(
true,
'Uncaught exception happened within the page. Run with --debug.'
)
})
return this.page
}

/**
* Run the tests
*
* @param {Page} page
* @param {string} file - file to load in the page
*/
async runTests(page, file) {
async runTests(page) {
await page.evaluate(async () => {
const regs = await navigator.serviceWorker.getRegistrations()
return regs[0] ? regs[0].unregister() : Promise.resolve()
})
await page.addScriptTag({ url: 'setup.js' })
await page.evaluate(
`localStorage.debug = "${this.env.DEBUG},-pw:*,-mocha:*"`
)
const files = []
const { outName, files: mainFiles } = await this.compiler()
files.push(...mainFiles)

switch (this.options.mode) {
case 'main': {
await page.addScriptTag({ url: file })
await page.addScriptTag({ url: outName })
break
}
case 'worker': {
// do not await for the promise because we will wait for the 'worker' event after
page.evaluate(addWorker(file))
page.evaluate(addWorker(outName))
break
}
default:
Expand All @@ -233,16 +248,21 @@ export class Runner {

// inject and register the service
if (this.options.sw) {
await compileSw(this, {
const { files: swFiles } = await compileSw(this, {
entry: this.options.sw,
out: 'sw-out.js',
})
await page.evaluate(() => navigator.serviceWorker.register('/sw-out.js'))
files.push(...swFiles)
await page.evaluate(() => {
navigator.serviceWorker.register(`/sw-out.js`)
return navigator.serviceWorker.ready
})
}

return { outName, files }
}

async run() {
let spinner = ora(`Setting up ${this.options.browser}`).start()
const spinner = ora(`Setting up ${this.options.browser}`).start()

try {
// get the context
Expand All @@ -255,29 +275,15 @@ export class Runner {

// get the page
const page = await this.setupPage(context)

// uncaught rejections
page.on('pageerror', (err) => {
console.error(err)
this.stop(
true,
'Uncaught exception happened within the page. Run with --debug.'
)
})
spinner.succeed(`${this.options.browser} set up`)

// bundle tests
spinner = ora('Bundling tests').start()
const file = await this.compiler()
spinner.succeed()

// run tests
await this.runTests(page, file)

console.log('run')
const { outName } = await this.runTests(page)
// Re run on page reload
if (this.options.debug) {
page.on('load', async () => {
await this.runTests(page, file)
await this.runTests(page)
})
} else {
// wait for the tests
Expand All @@ -294,7 +300,7 @@ export class Runner {

// coverage
if (this.options.cov && page.coverage) {
await createCov(this, await page.coverage.stopJSCoverage(), file)
await createCov(this, await page.coverage.stopJSCoverage(), outName)
}

// exit
Expand All @@ -315,14 +321,13 @@ export class Runner {
const page = await context.newPage()
await page.goto(this.url + 'before.html')

page.on('console', redirectConsole)
page.on('pageerror', (err) => {
this.stop(true, `Before page:\n ${err}`)
})

page.on('console', redirectConsole)

const file = await this.compiler('before')
await page.addScriptTag({ url: file })
const { outName } = await this.compiler('before')
await page.addScriptTag({ url: outName })
await page.waitForFunction('self.PW_TEST.beforeEnded', {
timeout: 0,
})
Expand All @@ -337,11 +342,19 @@ export class Runner {
await this.runBefore(context)
}
const page = await this.setupPage(context)
page.on('pageerror', console.error)

spinner.succeed()

this.compiler('watch')
const { files } = await this.runTests(page)

const watcher = watch([...files], {
ignored: /(^|[/\\])\../,
ignoreInitial: true,
awaitWriteFinish: { pollInterval: 100, stabilityThreshold: 1000 },
}).on('change', async () => {
await page.reload()
const { files } = await this.runTests(page)
watcher.add([...files])
})
}

/**
Expand Down Expand Up @@ -389,7 +402,7 @@ export class Runner {
* Compile tests
*
* @param {"before" | "bundle" | "watch"} mode
* @returns {Promise<string>} file to be loaded in the page
* @returns {Promise<import('./types').CompilerOutput>} file to be loaded in the page
*/
async compiler(mode = 'bundle') {
//
Expand Down
5 changes: 5 additions & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ export type PwResult<TBrowser> = TBrowser extends 'webkit'
: TBrowser extends 'chromium'
? ChromiumBrowser
: never

export interface CompilerOutput {
outName: string
files: Set<string>
}
36 changes: 26 additions & 10 deletions src/utils/build-sw.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/* eslint-disable no-console */

import { writeFileSync } from 'fs'
import path from 'path'
import { build } from 'esbuild'
import mergeOptions from 'merge-options'
import { fileURLToPath } from 'url'
import { writeSync } from 'tempy'

const merge = mergeOptions.bind({
ignoreUndefined: true,
Expand All @@ -22,41 +22,57 @@ const merge = mergeOptions.bind({
*
* @param {import("../runner").Runner} runner
* @param {{
* out: string,
* entry: string
* }} opts - Runner esbuild config
*/
export async function compileSw(runner, { out, entry }) {
const outfile = path.join(runner.dir, out)
const infile = path.join(runner.dir, 'in.js')
const infileContent = `
export async function compileSw(runner, { entry }) {
const files = new Set()
const outName = 'sw-out.js'
const outPath = path.join(runner.dir, outName)
const content = `
process.env = ${JSON.stringify(runner.env)}
self.addEventListener('install', function(event) {
self.skipWaiting();
})
self.addEventListener('activate', (event) => {
return self.clients.claim()
})
import "${path.join(runner.options.cwd, entry).replace(/\\/g, '/')}"
`
const entryPoint = writeSync(content, { extension: 'js' })
/** @type {ESBuildPlugin} */
const watchPlugin = {
name: 'watcher',
setup(build) {
// @ts-ignore
build.onLoad({ filter: /.*/, namespace: 'file' }, (args) => {
if (args.path !== outPath && args.path !== entryPoint) {
files.add(args.path)
}
})
},
}

writeFileSync(infile, infileContent)
/** @type {ESBuildOptions} */
const defaultOptions = {
entryPoints: [infile],
entryPoints: [entryPoint],
bundle: true,
format: 'esm',
sourcemap: 'inline',
plugins: [watchPlugin],
inject: [
path.join(
path.dirname(fileURLToPath(import.meta.url)),
'inject-process.js'
),
],
outfile,
outfile: outPath,
define: {
global: 'globalThis',
},
}
await build(merge(defaultOptions, runner.options.buildSWConfig))

return out
return { files, outName }
}
Loading

0 comments on commit fbfd613

Please sign in to comment.