Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules/
yarn.lock
upstream/
9 changes: 9 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# wpt-fd98377 sha256:b00e229cb3ed89596d68d22c488abce63c18f308e172f5182d44e26465cf4a10
[submodule "upstream/wpt"]
ignore = dirty
ref = fd983776a7cd19ebcda7a2bcb69c74330ee5d8c9
path = upstream/wpt
url = https://github.com/web-platform-tests/wpt.git
branch = master
shallow = true
sparse-checkout = common css/css-conditional/js css/css-forms/parsing css/css-highlight-api css/css-multicol/parsing css/css-overflow/parsing css/css-pseudo/parsing css/css-shadow css/css-view-transitions/parsing css/selectors css/support custom-elements dom/nodes fullscreen/rendering html/browsers/browsing-the-web/scroll-to-fragid html/dom/elements/global-attributes html/resources html/semantics/forms html/semantics/popovers html/semantics/sections html/semantics/selectors inert quirks resources shadow-dom webvtt/api
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,11 @@
"url": "https://github.com/dperini/nwsapi.git"
},
"scripts": {
"lint": "eslint ./src/nwsapi.js",
"test": "node --test test/repo/integration/maintenance.test.mts",
"lint": "eslint ./src/nwsapi.js"
"wpt:serve": "node test/wpt/wpt-launcher.mjs serve",
"wpt:setup": "node test/wpt/wpt-launcher.mjs setup",
"wpt:verify": "node test/wpt/wpt-launcher.mjs verify"
},
"devDependencies": {
"acorn": "8.15.0",
Expand Down
21 changes: 12 additions & 9 deletions test/wpt/router.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,20 @@ function browserRoot(string $nwsapiRoot): string
return $resolved;
}

$nwsapiRoot = dirname(__DIR__, 2);
$nwsapiRoot = getenv('NWSAPI_ROOT') ?: dirname(__DIR__, 2);
$browserRoot = browserRoot($nwsapiRoot);
$requestPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';

if ($requestPath === '/') {
header('Content-Type: text/html; charset=UTF-8');
readfile(__DIR__ . DIRECTORY_SEPARATOR . 'index.html');
exit;
}
if ($requestPath === '/favicon.svg') {
header('Content-Type: image/svg+xml');
readfile(__DIR__ . DIRECTORY_SEPARATOR . 'favicon.svg');
exit;
}
$decodedPath = rawurldecode($requestPath);

if (!str_starts_with($decodedPath, '/')) {
Expand All @@ -60,14 +71,6 @@ function browserRoot(string $nwsapiRoot): string
$realRequested = realpath($requestedFile);
$isInsideBrowserRoot = $realRequested !== false && ($realRequested === $browserRoot || str_starts_with($realRequested, $rootPrefix));

// A manually entered directory or one reached from a link always shows
// its corresponding tree without redirects: Back/Forward work normally.
if ($isInsideBrowserRoot && is_dir($realRequested)) {
$_SERVER['WPT_BROWSER_PATH'] = $relativePath;
require $browserRoot . DIRECTORY_SEPARATOR . 'wpt-browser.php';
exit;
}

// Let the PHP server handle every file other than the harness.
if (!preg_match('~(?:^|/)resources/testharness\.js$~', $requestPath)) {
return false;
Expand Down
78 changes: 78 additions & 0 deletions test/wpt/wpt-launcher.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/usr/bin/env node
import { execFileSync, spawnSync } from 'node:child_process'
import { createHash } from 'node:crypto'
import { existsSync, mkdirSync, readFileSync } from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'

const root = fileURLToPath(new URL('../../', import.meta.url))
const maxBuffer = 512 * 1024 * 1024
const modules = path.join(root, '.gitmodules')
const prefix = 'submodule.upstream/wpt.'
const get = key => execFileSync('git', ['config', '--file', modules, '--get', prefix + key], { encoding: 'utf8' }).trim()
const config = { path: get('path'), ref: get('ref'), url: get('url'), branch: get('branch'), sparse: get('sparse-checkout').split(/\s+/) }
const manifest = readFileSync(modules, 'utf8').match(/^#\s+wpt-[\w-]+\s+sha256:([0-9a-f]{64})$/m)?.[1]
const upstream = path.join(root, config.path)

if (
config.path !== 'upstream/wpt' ||
!/^[0-9a-f]{40}$/.test(config.ref) ||
!config.url.startsWith('https://') ||
!/^[A-Za-z0-9._/-]+$/.test(config.branch) ||
!manifest ||
config.sparse.some(entry => entry.startsWith('-'))
) {
throw new Error('Invalid upstream/wpt pin in .gitmodules')
}

switch (process.argv[2]) {
case 'setup': setup(); break
case 'verify': verify(); break
case 'serve': verify(); serve(); break
default: console.error('Usage: node test/wpt/wpt-launcher.mjs <setup|verify|serve>'); process.exitCode = 1
}

function git(args) {
execFileSync('git', args, { cwd: root, stdio: 'inherit' })
}

function phpInstallHint() {
switch (process.platform) {
case 'darwin': return 'Install it with Homebrew: brew install php'
case 'linux': return 'Install the PHP CLI with your distribution package manager, for example: sudo apt install php-cli'
case 'win32': return 'Install PHP with winget, then restart the terminal so php is on PATH.'
default: return 'Install the PHP CLI and make the php command available on PATH.'
}
}

function serve() {
const php = spawnSync('php', ['--version'], { stdio: 'ignore' })
if (php.error || php.status !== 0) {
throw new Error(`PHP is required for the interactive WPT server. ${phpInstallHint()}`)
}
const result = spawnSync('php', ['-S', `localhost:${process.env.NWSAPI_WPT_PORT || '8000'}`, '-t', upstream, path.join(root, 'test/wpt/router.php')], {
cwd: root, env: { ...process.env, BROWSER_ROOT: upstream, NWSAPI_ROOT: root }, stdio: 'inherit',
})
if (result.error) throw result.error
process.exitCode = result.status ?? 1
}

function setup() {
if (existsSync(upstream)) return verify()
mkdirSync(path.dirname(upstream), { recursive: true })
git(['clone', '--depth', '1', '--single-branch', '--filter=blob:none', '--no-checkout', '--branch', config.branch, config.url, upstream])
git(['-C', upstream, 'sparse-checkout', 'set', '--cone', '--', ...config.sparse])
git(['-C', upstream, 'fetch', '--depth', '1', '--filter=blob:none', 'origin', config.ref])
git(['-C', upstream, 'checkout', '--detach', 'FETCH_HEAD', '--'])
verify()
}

function verify() {
const head = existsSync(upstream) ? execFileSync('git', ['-C', upstream, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim() : null
if (head !== config.ref) throw new Error(`${config.path} is not pinned at ${config.ref}; run npm run wpt:setup`)
if (!existsSync(path.join(upstream, 'resources', 'testharness.js'))) throw new Error(`${config.path} does not contain the WPT harness resources`)
if (execFileSync('git', ['-C', upstream, 'status', '--porcelain'], { encoding: 'utf8' }) !== '') throw new Error(`${config.path} is dirty; refusing to run modified upstream tests`)
const tree = execFileSync('git', ['-C', upstream, '-c', 'core.quotePath=false', 'ls-tree', '-r', config.ref], { maxBuffer })
if (createHash('sha256').update(tree).digest('hex') !== manifest) throw new Error(`${config.path} does not match its pinned tree manifest`)
}
42 changes: 4 additions & 38 deletions test/wpt/wpt.sh
Original file line number Diff line number Diff line change
@@ -1,41 +1,7 @@
#!/bin/sh
set -eu

path=`dirname $0`
# The launcher serves the pinned upstream/wpt checkout on localhost:8000.

#
# NOTE: All this is preliminary work in progress.
#
# Both wpt and nwsapi repositories must be installed
# side by side at the same folder level to have them
# work together smoothly as intended for these tests.
#
# A working instance of PHP is needed to setup the
# local server, then from the main nwsapi folder
# execute the provided shell to setup for testing
# using the following terminal command:
#
# test/wpt/wpt.sh
#
# this will start the PHP local server and listen
# for browser connections on port 8000, after this
# you should be able to open the followeing URL:
#
# http://localhost:8000
#
# After loading one of the tests, you willl have the
# option to execute it using the browser internal QS
# API or nwsapi alternatedly by pressing the reload
# button (CTRL-R in browsers), or the RERUN button
# found in each of the test pages itself.
#
# Each reload will change the environment used
# from browser intrernal QS api to nwsapi
#

cp ./test/wpt/favicon.svg ../wpt/
cp ./test/wpt/index.html ../wpt/

# start an instance of the PHP internal httpd server
# needed to interactively execute WPT tests in browsers

php -S localhost:8000 -t ../wpt/ ./test/wpt/router.php
root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)
exec node "$root/test/wpt/wpt-launcher.mjs" serve
Loading