joyrider v1.0.0
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 thereceptacle
option istrue
, applications are rendered into this temporary directory as well.
- When the
- 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. Asyncteardown
helper (e.g. as available in Node-Tap, Tape, Brittle)dht
- Optional. Object. Options for a locally hosted Hyperswarm DHT, as provided by testnetvars
- Optional. Object. Define template locals for the givenapp
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 generifyreceptacle
- Optional. Boolean. This will betrue
ifvars
is defined, otherwise it defaults tofalse
. 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 thevars
option to an empty object ({}
).env
- Optional. Object. Default:process.env
- environment variables to be injected into the applicationshow
- 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 needshow
to be set totrue
. Setting show to true can also be useful in debugging test cases, especially when combined withride.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 (essentiallyholepunch dev
mode). If a string thefrom
option must be eitherstaged
orreleased
. Iffrom
isstaged
theopen
method will automatically stage, seed and then launch the application from that seed key. Iffrom
isreleased'
theopen
method will automatically stage, release, seed and then launch the application from that seed key. This can also be achieved manually with theride.stage()
,ride.release()
,ride.seed()
andride.launch()
methods.network
- Object{ record: Boolean }
. Ifnetwork.record
is true the network requests will be recorded (much like pressing record in the network tab of Chrome Devtools). Seeride.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 aninspect
instance for the main entry point iftrue
, returnnull
iffalse
. If the inspect option is a function, string or regular expression the returnedinspect
instance will be selected based on the predicate as passed toride.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 belatest
(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 }
. Ifnetwork.record
is true the network requests will be recorded (much like pressing record in the network tab of Chrome Devtools). Seeride.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 aninspect
instance for the main entry point iftrue
, returnnull
iffalse
. If the inspect option is a function, string or regular expression the returnedinspect
instance will be selected based on the predicate as passed toride.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 }
. Ifnetwork.record
is true the network requests will be recorded (much like pressing record in the network tab of Chrome Devtools). Seeride.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 bevisible
orhidden
.
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 befocused
orblurred
.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 totrue
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 totrue
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 tofalse
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
preview
- Boolean. Defaultfalse
. Returns a Preview Object as per https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-ObjectPreviewdescribe
- Boolean. Defaultfalse
. Setting totrue
implicitly enablespreview
. Returns theproperties
(orentries
in the case of Map and Set instances) property of the Preview Object.
const result = await inspect.run(str, options)
Run some code within the target global scope.
May be multiline, supports use
Options
preview
- Boolean. Defaultfalse
. Returns a Preview Object as per https://chromedevtools.github.io/devtools-protocol/tot/Runtime/#type-ObjectPreviewawaitPromise
- Boolean. Defaultfalse
. If the last line ofstr
is a Promise, return the result of the promise instead of the promise object.
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. Defaultfalse
. Passesnode.backendNodeId
instead ofnode.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. Defaulttrue
. Whether to describe iframes and shadow DOMs within the tree of the suppliednode
.depth
- Interger. Default:-1
(Infinite). The max depth to traverse in the tree of the suppliednode
.
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 anode
object. Resolve after the subtree of a given node is modified.attribute-modified
-value
argument should be anode
object. Resolve after an attribute on the given node is modified.node-removed
-value
argument should be anode
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
andxhr
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