1.0.0 • Published 1 year ago

joyrider v1.0.0

Weekly downloads
-
License
BSD-3-Clause
Repository
github
Last release
1 year ago

Joyrider

Holepunch Test Driver

Terms

  • ESM - EcmaScript Modules (import blah from 'blah')
  • CJS - CommonJS Module (const blah = require('blah'))

Accompanying Documentation

API

ESM: import joyrider from 'joyrider'

CJS: const joyrider = require('joyrider')

const rider = joyrider(import.meta.url) / const rider = joyrider(__filename)

Instantitate Joyrider at the top level of a file (for example, a test file) by passing in the __filename global in CJS or import.meta.url in ESM.

This returns an async function which is linked to the file, we're calling this function: rider.

const ride = await rider(options)

Call rider and await the returned promise to create a test mode session, which we're calling ride.

It's recommended to create one session per test case.

A test mode session consists of:

  • A local Hyperswarm DHT for Holepunch and applications to bootstrap from (instead of relying on a public DHT)
  • A temporary directory containing isolated Holepunch storage and configuration settings
    • When the vars option is provided or the receptacle option is true, applications are rendered into this temporary directory as well.
  • Seeding, Releasing and Launching sandboxed capabilities.
  • Opening any application from the file system or launching it from a seed.
  • Remote interaction and inspection capabilities for any Window or View in the specified launched/opened application via the Chrome Devtools Protocol

Options

  • app - Required. String. Path to a Holepunch application. May be relative or absolute. May be file path or file URL.
  • teardown - Optional. Function. Async teardown helper (e.g. as available in Node-Tap, Tape, Brittle)
  • dht - Optional. Object. Options for a locally hosted Hyperswarm DHT, as provided by testnet
  • vars - Optional. Object. Define template locals for the given app path, keys correspond to template variables names (__name__) and values are the value to interpolate in place of that template variable. Templating is provided by generify
  • receptacle - Optional. Boolean. This will be true if vars is defined, otherwise it defaults to false. This is for advanced cases where we want to copy a fixture project dir into a temporary folder, but have no need to interpolate any template variables. It's the exact same as setting the vars option to an empty object ({}).
  • env - Optional. Object. Default: process.env - environment variables to be injected into the application
  • show - Optional. Boolean. Default: false - The application is hidden while tests run, due to Electron limitations there may be some rare cases where it needs to be shown. Anything involving focusing/blurring showing/hiding of Windows of Views may need show to be set to true. Setting show to true can also be useful in debugging test cases, especially when combined with ride.debugPause()

await ride.close()

Close the session. If the teardown option is provided it is not necessary to call this method.

await ride.debugPause()

Utility function that returns a promise that never resolves and keeps the process open.

This can be useful when debugging, particularly in combination with the show option.

ride.testnet

A local Hyperswarm DHT instance for the test mode session. See testnet.

ride.entry

The application entry URL (Holepunch app: protocol URL).

ride.processes

Set. Processes created during the test mode session. Mainly for debugging purposes. These are automatically cleaned up during close (or in automatically via the teardown option).

ride.errors

Array. Errors collected during operations, these will be thrown as an AggregateError during close() - or via a supplied teardown function option where closing is automatic.

ride.targets

Array. An array Chrome Devtools Protocol targets as provided by Chrome Remote Interface List method. In Chrome Devtools Protocol vernacular a target handle for a content view (e.g. a Window or a View) which can be used to remotely connect to the inspector for that content view.

Mainly for any debugging purposes when attempting to connect to a target with ride.inspect().

ride.tmpDir

String. Test mode session temporary directory

ride.storageDir

String. Test mode session storage directory path

ride.configDir

String. Test mode session config directory path

ride.projectDir

String. Test mode session project directory path.

This will be the absolute application path, or when vars are provided the rendered application path within the test mode session temporary directory.

const inspect = await ride.open(options)

Opens the specified application in Holepunch. By default the open method loads the application from the file system rather than from "Hyperspace" (seeded Hypercores). The from option or the launch method can be used to open an application from Hyperspace.

The open method returns a Promise that resolves to an inspect instance for the application entry point. See ride.inspect() for more.

Options

  • from - String. If not set or falsy (null, undefined) the application is loaded from the file system (essentially holepunch dev mode). If a string the from option must be either staged or released. If from is staged the open method will automatically stage, seed and then launch the application from that seed key. If from is released' the open method will automatically stage, release, seed and then launch the application from that seed key. This can also be achieved manually with the ride.stage(), ride.release(), ride.seed() and ride.launch() methods.
  • network - Object { record: Boolean }. If network.record is true the network requests will be recorded (much like pressing record in the network tab of Chrome Devtools). See ride.network for how to access the recorded requests.
  • timeout - Number. Default 5000. Milliseconds before attempts to attach the inspector cease and an error is thrown.
  • inspect - Boolean or Predicate Function, String or RegExp. Default true. Return an inspect instance for the main entry point if true, return null if false. If the inspect option is a function, string or regular expression the returned inspect instance will be selected based on the predicate as passed to ride.inspect().

await ride.stage(channel)

Stage the specified application to a given channel. Staging is sandboxed (in ride.storageDir) so channel name collisions are only a concern per session.

await ride.release(channel)

Release the specified application to a given channel. Staging is sandboxed (in ride.storageDir) so channel name collisions are only a concern per session.

const seed = await ride.seed(channel)

Seed a staged or released application based on it's channel name. This returns seed instance.

await seed.stop()

Stop seeding.

seed.key

The application key being seeded (used to launch the application).

seed.prefetched

Boolean. Whether contents have been prefetched.

seed.phases

Array. Array of objects that correspond to the output of holepunch seed -c <channel> --json. The phases of seeding initialization are added to this array as they occur.

seed.connections

Array. Array of objects representing incoming connections.

seed.connectionFailures

Array. Array of objects representing connections failures.

seed.process

The child process that is running the seed.

const inspect = await ride.launch(key[, options])

Launch an application from a given key.

By default, the launch method returns a Promise that resolves to an inspect instance for the application entry point. See ride.inspect() for more.

Options

  • checkout - String | Number. Checkout a particular version of a launched app. May be latest (to get the latest staged version), release (to get the the latest released version) or an integer representing a specific Hypercore seq to check out.
  • network - Object { record: Boolean }. If network.record is true the network requests will be recorded (much like pressing record in the network tab of Chrome Devtools). See ride.network for how to access the recorded requests.
  • timeout - Number. Default 5000. Milliseconds before attempts to attach the inspector cease and an error is thrown.
  • inspect - Boolean or Predicate Function, String or RegExp. Default true. Return an inspect instance for the main entry point if true, return null if false. If the inspect option is a function, string or regular expression the returned inspect instance will be selected based on the predicate as passed to ride.inspect().

const inspect = await ride.inspect(predicate[, options])

Connect to any Window or View based on predicate which may be a Function, String or Regular Expression.

If predicate is a function, it will be passed each target (see ride.targets). The predicate function must return true to select a target. const inspect = await ride.inspect(({ url })) => url === 'app://app/my-page.html')

If predicate is a Regular Expression the first target with a URL that the Regular Expression matches against will be selected for inspection. const inspect = await ride.inspect(/my-page/)

If predicate is a string, it is converted internally to a Regular Expression that matches the string against the end of the target URL, and the first target that matches is selected for inspection. const inspect = await ride.inspect('my-page.html')

Returns an inspect instance which can be used to interact with the selected target (Window or View).

The inspect instance has some convenience methods but full Chrome Devtools Protocol functionality is provided via the inspect.protocol instance, which is an instance of Chrome Remote Interface.

The inspect.network convenience API is also provided for easy querying of network requests.

Options

  • network - Object { record: Boolean }. If network.record is true the network requests will be recorded (much like pressing record in the network tab of Chrome Devtools). See ride.network for how to access the recorded requests.
  • timeout - Number. Default 5000. Milliseconds before attempts to attach the inspector cease and an error is thrown.

inspect.protocol

An instance of Chrome Remote Interface. Use this for full Chrome Devtools Protocol usage.

inspect.target

The target (Window or View) being inspected. See ride.targets.

inspect.document

The document root node Chrome Devtools Protocol object, as supplied by the root property of await inspect.protocol.DOM.getDocument() (shallow). The inspect.document.nodeId property is useful for other DOM methods (see https://chromedevtools.github.io/devtools-protocol/tot/DOM/).

const bool = await inspect.verify()

Confirm the inspector is connected to a Holepunch application View or Window. Resolves to true if so and false if not.

const bool = await inspect.visible()

Resolves to true if the inspected Window or View is visible (shown), false otherwise.

  • expect. String. Default: visible. Indicates the expected state, this is used to determine whether to wait for a visibility to change to the expected state (or 2 seconds whichever is greater) before checking the state. May be visible or hidden.

const bool = await inspect.focused(options)

Resolves to true if the inspected Window or View is focused, false otherwise.

Options

  • expect. String. Default: focused. Indicates the expected state, this is used to determine whether to wait for focus to change to the expected state (or 2 seconds whichever is greater) before checking the state. May be focused or blurred.
  • contentView. Boolean. Default: false. The default Holepunch Window is actually a Window with a content view. In rare cases when we want to check the focus state of the internal content view, this option can be used.

const bool = await inspect.minimized(options)

Resolves to true if the inspected Window is minimized, false otherwise. The target is a View, the result will always be false.

Options

  • waitForChange. Boolean. Default: false. Set to true to wait for either the minimize or restore events (or 2 seconds, whichever is faster) to fire on the target before checking if it is minimized.

const bool = await inspect.maximized(options)

Resolves to true if the inspected Window is maximized, false otherwise. The target is a View, the result will always be false.

Options

  • waitForChange. Boolean. Default: false. Set to true to wait for either the maximize or unmaximize events (or 2 seconds, whichever is faster) to fire on the target before checking if it is maximized.

const { x, y, width, height } = await inspect.dimensions()

Resolves to an object containing the bounds of a Window or View.

await inspect.reload(options)

Reload the page within the Window or View.

Options

  • ignoreCache - Boolean. Default: true. Set to false to reload from cache.

await inspect.loaded(url)

Ensure that a given URL to a JavaScript file in the inspected target has been loaded: await inspect.loaded('app://app/my-file.js')

May also be a url fragment where the base is app://app: await inspect.loaded('my-file.js'), await inspect.loaded('sub/path/my-file.js')

If the page does not load the specified URL, this method will cause it to be loaded.

The extension must be .js.

const exports = await inspect.exports(url, options)

Describes the exports of JS file as specified by URL. The url may also be a url fragment: await inspect.exports('my-file.js').

If the URL or URL fragment has an extension it must be .js.

By default returns an object with keys representing export names and values containing primitive values (e.g. strings, numbers) or else empty strings (functions, objects).

The preview and describe options can be used to retrive type information about the exports.

This is primarily useful for test cases that export primitives which can then be asserted against in the parent test process.

Options

const result = await inspect.run(str, options)

Run some code within the target global scope.

May be multiline, supports use

Options

const node = await inspect.querySelector(selector, root)

Similar to document.querySelector in that the selector string works the same way (selecting DOM elements based on CSS notation), but resolves to an object containing a nodeId which can passed into other inspect and inspect.protocol methods.

If root is undefined, this convenience method runs querySelector on the root document node, otherwise runs query selector on the passed node as the root.

See https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-querySelector

const nodes = await inspect.querySelectorAll(selector, root)

Similar to document.querySelectorAll in that the selector string works the same way (selecting DOM elements based on CSS notation), but resolves to an array of objects containing nodeId properties. These node objects can passed into other inspect and inspect.protocol methods.

If root is undefined, this convenience method runs querySelectorAll on the root document node, otherwise runs query selector on the passed node as the root.

See https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-querySelectorAll

const value = await inspect.value(node)

Retrieve the value of a given node (e.g. of an input element).

await inspect.value(node, value)

Set the value of a given node (e.g. of an input element).

const attributes = await inspect.attributes(node, options)

Resolves to an object whose keys represent DOM attribute names and values DOM attribute values (or empty strings where only the name is set). If options.pierce is set to true, the backendNodeId field of the node will be used, this allows to get the attributes of a node with nodeId === 0.

The node object must contain nodeId or backendNodeId.

This convenience method returns an object instead of an interleaved array

See https://chromedevtools.github.io/devtools-protocol/tot/DOM/#method-getAttributes

Options

  • pierce - Boolean. Default false. Passes node.backendNodeId instead of node.nodeId to get the attributes of nodes that may not have been pushed to the front-end.

See: https://chromedevtools.github.io/devtools-protocol/tot/DOM/#type-BackendNodeId

await inspect.selectFile(node, file[, ...files])

Set a file input selected files based on supplied paths. The node argument must be for a file input.

One or more file paths can be specified as subsequent arguments.

This convenience method wraps inspect.protocol.setInputFiles and allows for relative or absolute file paths.

await inspect.click(node)

Simulate a click event on a given DOM element.

await inspect.innerText(node)

Get the innerText of a DOM element as a string.

await inspect.innerHTML(node)

Get the innerHTML of a DOM element as a string.

await inspect.outerHTML(node)

Get the outerHTML of a DOM element as a string.

await inspect.computedStyle(node, pick)

Get the computed styles for a DOM element. Returns an object, the keys are CSS properties and the values are CSS values. The pick argument can be used to get a specific computed style, for example await inspect.computedStyle(node, 'background-color').

await inspect.describe(node, opts)

Provide various metadata for a DOM element, as per https://chromedevtools.github.io/devtools-protocol/tot/DOM/#type-Node.

Options

  • pierce - Boolean. Default true. Whether to describe iframes and shadow DOMs within the tree of the supplied node.
  • depth - Interger. Default: -1 (Infinite). The max depth to traverse in the tree of the supplied node.

await inspect.after(type, value, count = 1)

A convenience method for applying control-flow to specialized event-based-breakpoints. The returned promise will resolve just after the particular event type has occurred.

The type argument should be a string, types are listed in the following subheading. The value argument is polymorphic, it depends on the type as to what it should be. The count argument will wait for the event to fire as many times as count specifies.

If count is 1 (default) then the return value of this method is an object containing metadata about the event/breakpoint. If count is >1 then the return value is an array of objects containing metadata about the event/breakpoint. The metadata returned depends on the type provided.

Types

  • subtree-modified - value argument should be a node object. Resolve after the subtree of a given node is modified.
  • attribute-modified - value argument should be a node object. Resolve after an attribute on the given node is modified.
  • node-removed - value argument should be a node object. Resolve after the subtree of a given node is modified.
  • event - RESERVED. NOT IMPLEMENTED. value argument should be a string representing the event name to break on. Resolve after a particular DOM event occurs.
  • fetch / xhr - RESERVED. NOT IMPLEMENTED. value argument should be a string representing the part of a URL to break on when an XHR or Fetch occurs (fetch and xhr types are synonymous aliases). Resolve after a particular DOM event occurs.
  • style-update - RESERVED. NOT IMPLEMENTED. value argument should be an array of strings, representing CSS properties to track. Resolve after matched style properties change.

const process = await inspect.ensureSidecar()

Spawns Holepunch Sidecar process (runs holepunch tools sidecar) if it isn't already running. Generally this method does not need to be manually called since the sidecar starts automatically when needed. It could be used to get a reference to the sidecar process, or be used to get a reference to a new sidecar process after a former sidecar process has been killed.

await inspect.killSidecar()

Kills the Holepunch Sidecar process.

const stopRecording = await inspect.network.record()

Start recording network requests. To record all network requests from page load, use the network option in ride.inspect, ride.open or ride.launch. This method is mostly useful for recording network requests after page load while excluding requests during page load.

Returns a stopRecording function, which ceases recording of network requests.

inspect.network.stopRecording()

Ceases recording of network requests.

inspect.network.recording

Boolean. true if recording, false if not.

inspect.network.requests

Array. Request objects as per https://chromedevtools.github.io/devtools-protocol/tot/Network/#event-requestWillBeSent.

await inspect.network.tally(amount[, timeout = 5000])

Wait for a specified amount of requests before a timeout.

const body = await inspect.network.responseBody(predicate)

Resolves to a string representing the response body of a request selected per the predicate argument.

If predicate is a function, it will be passed each request object. The predicate function must return true to select a target. const body = await inspect.network.responseBody(({ request })) => request.url === 'app://app/my-page.html')

If predicate is a Regular Expression the first target with a URL that the Regular Expression matches against will be selected for inspection. const body = await inspect.network.responseBody(/my-page/)

If predicate is a string, it is converted internally to a Regular Expression that matches the string against the end of the target URL, and the first target that matches is selected for inspection. const body = await inspect.network.responseBody('my-page.html')

inspect.network.on('request', fn)

Call fn with a request object every time a request occurs.

inspect.network is an EventEmitter so other methods such as once and functions such as events.once can also be used.

inspect.network.on('initial-loading-request', fn)

Call fn with a request object every time a request occurs during page load.

inspect.network is an EventEmitter so other methods such as once and functions such as events.once can also be used.

inspect.network.on('post-loading-request', fn)

Call fn with a request object every time a request occurs after page load.

inspect.network is an EventEmitter so other methods such as once and functions such as events.once can also be used.

env

JOYRIDER_VERBOSE=1 || JRV=1

Setting JOYRIDER_VERBOSE (alias JRV) will make subprocesses inherit STDIO. This can be useful when debugging seeding, staging, launching/opening errors.

Licence

MIT

Todo

  • improve input validation
  • tests