diff --git a/build/wpt_test.bzl b/build/wpt_test.bzl index c6785920550..c68557bd941 100644 --- a/build/wpt_test.bzl +++ b/build/wpt_test.bzl @@ -295,6 +295,8 @@ const unitTests :Workerd.Config = ( (name = "SIDECAR_HOSTNAME", fromEnvironment = "SIDECAR_HOSTNAME"), (name = "HTTP_PORT", fromEnvironment = "HTTP_PORT"), (name = "HTTPS_PORT", fromEnvironment = "HTTPS_PORT"), + (name = "WS_PORT", fromEnvironment = "WS_PORT"), + (name = "WSS_PORT", fromEnvironment = "WSS_PORT"), (name = "GEN_TEST_CONFIG", fromEnvironment = "GEN_TEST_CONFIG"), (name = "GEN_TEST_REPORT", fromEnvironment = "GEN_TEST_REPORT"), (name = "GEN_TEST_STATS", fromEnvironment = "GEN_TEST_STATS"), diff --git a/justfile b/justfile index 4f89c863464..b40cad9a61c 100644 --- a/justfile +++ b/justfile @@ -77,7 +77,7 @@ new-wpt-test test_name: echo 'wpt_test(name = "{{test_name}}", config = "{{test_name}}-test.ts", wpt_directory = "@wpt//:{{test_name}}@module")' >> src/wpt/BUILD.bazel ./tools/cross/format.py - bazel test //src/wpt:{{test_name}} --test_env=GEN_TEST_CONFIG=1 --test_output=streamed + bazel test //src/wpt:{{test_name}}@ --test_env=GEN_TEST_CONFIG=1 --test_output=streamed # Specify the full Bazel target name for the test to be created. # e.g. just new-test //src/workerd/api/tests:v8-temporal-test diff --git a/src/wpt/BUILD.bazel b/src/wpt/BUILD.bazel index 86196d08d97..912ad41b3a3 100644 --- a/src/wpt/BUILD.bazel +++ b/src/wpt/BUILD.bazel @@ -129,3 +129,37 @@ wpt_test( config = "performance-timeline-test.ts", wpt_directory = "@wpt//:performance-timeline@module", ) + +wpt_test( + name = "websockets", + size = "large", + config = "websockets-test.ts", + start_server = True, + target_compatible_with = select({ + # TODO(later): Provide a Windows version of the sidecar script we wrote to invoke wptserve. + # Currently we only have a Unix shell script. + "@platforms//os:windows": ["@platforms//:incompatible"], + "//conditions:default": [], + }), + wpt_directory = "@wpt//:websockets@module", +) + +wpt_test( + name = "eventsource", + size = "large", + config = "eventsource-test.ts", + start_server = True, + target_compatible_with = select({ + # TODO(later): Provide a Windows version of the sidecar script we wrote to invoke wptserve. + # Currently we only have a Unix shell script. + "@platforms//os:windows": ["@platforms//:incompatible"], + "//conditions:default": [], + }), + wpt_directory = "@wpt//:eventsource@module", +) + +wpt_test( + name = "webidl", + config = "webidl-test.ts", + wpt_directory = "@wpt//:webidl@module", +) diff --git a/src/wpt/eventsource-test.ts b/src/wpt/eventsource-test.ts new file mode 100644 index 00000000000..2f003e38e26 --- /dev/null +++ b/src/wpt/eventsource-test.ts @@ -0,0 +1,251 @@ +// Copyright (c) 2017-2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +import { type TestRunnerConfig } from 'harness/harness'; + +export default { + 'dedicated-worker/eventsource-close.js': { + comment: 'Uses postMessage which is not available in workerd', + omittedTests: true, + }, + 'dedicated-worker/eventsource-close2.js': { + comment: 'Uses self.close() which is not available in workerd', + omittedTests: true, + }, + 'dedicated-worker/eventsource-constructor-no-new.any.js': {}, + 'dedicated-worker/eventsource-constructor-non-same-origin.js': { + comment: 'Uses postMessage which is not available in workerd', + omittedTests: true, + }, + 'dedicated-worker/eventsource-constructor-url-bogus.js': { + comment: 'Uses postMessage which is not available in workerd', + omittedTests: true, + }, + 'dedicated-worker/eventsource-eventtarget.worker.js': { + comment: 'Uses importScripts which is not available in workerd', + omittedTests: true, + }, + 'dedicated-worker/eventsource-onmesage.js': { + comment: 'Uses postMessage which is not available in workerd', + omittedTests: true, + }, + 'dedicated-worker/eventsource-onopen.js': { + comment: 'Uses postMessage which is not available in workerd', + omittedTests: true, + }, + 'dedicated-worker/eventsource-prototype.js': { + comment: 'Uses postMessage which is not available in workerd', + omittedTests: true, + }, + 'dedicated-worker/eventsource-url.js': { + comment: 'Uses postMessage which is not available in workerd', + omittedTests: true, + }, + 'event-data.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'eventsource-close.window.js': { + comment: 'Window-specific tests are not supported', + omittedTests: true, + }, + 'eventsource-constructor-document-domain.window.js': { + comment: 'Window-specific tests are not supported', + omittedTests: true, + }, + 'eventsource-constructor-empty-url.any.js': { + comment: + 'workerd throws SyntaxError for empty URL instead of resolving to self.location', + expectedFailures: ['EventSource constructor with an empty url.'], + }, + 'eventsource-constructor-non-same-origin.window.js': { + comment: 'Window-specific tests are not supported', + omittedTests: true, + }, + 'eventsource-constructor-stringify.window.js': { + comment: 'Window-specific tests are not supported', + omittedTests: true, + }, + 'eventsource-constructor-url-bogus.any.js': {}, + 'eventsource-cross-origin.window.js': { + comment: 'Window-specific tests are not supported', + omittedTests: true, + }, + 'eventsource-eventtarget.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'eventsource-onmessage-trusted.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'eventsource-onmessage.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'eventsource-onopen.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'eventsource-prototype.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'eventsource-reconnect.window.js': { + comment: 'Window-specific tests are not supported', + omittedTests: true, + }, + 'eventsource-request-cancellation.window.js': { + comment: 'Window-specific tests are not supported', + omittedTests: true, + }, + 'eventsource-url.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-bom-2.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-bom.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-comments.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-data-before-final-empty-line.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-field-data.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-field-event-empty.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-field-event.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-field-id-2.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-field-id-3.window.js': { + comment: 'Window-specific tests are not supported', + omittedTests: true, + }, + 'format-field-id-null.window.js': { + comment: 'Window-specific tests are not supported', + omittedTests: true, + }, + 'format-field-id.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-field-parsing.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-field-retry-bogus.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-field-retry-empty.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-field-retry.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-field-unknown.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-leading-space.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-mime-bogus.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-mime-trailing-semicolon.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-mime-valid-bogus.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-newlines.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-null-character.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'format-utf-8.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'request-accept.any.js': { + comment: 'EventSource does not support relative URLs in workerd', + expectedFailures: true, + }, + 'request-cache-control.any.js': { + comment: 'Test times out due to relative URL handling', + omittedTests: true, + }, + 'request-credentials.window.js': { + comment: 'Window-specific tests are not supported', + omittedTests: true, + }, + 'request-redirect.window.js': { + comment: 'Window-specific tests are not supported', + omittedTests: true, + }, + 'request-status-error.window.js': { + comment: 'Window-specific tests are not supported', + omittedTests: true, + }, + 'shared-worker/eventsource-close.js': { + comment: 'SharedWorker tests are not applicable to workerd', + omittedTests: true, + }, + 'shared-worker/eventsource-constructor-non-same-origin.js': { + comment: 'SharedWorker tests are not applicable to workerd', + omittedTests: true, + }, + 'shared-worker/eventsource-constructor-url-bogus.js': { + comment: 'SharedWorker tests are not applicable to workerd', + omittedTests: true, + }, + 'shared-worker/eventsource-eventtarget.js': { + comment: 'SharedWorker tests are not applicable to workerd', + omittedTests: true, + }, + 'shared-worker/eventsource-onmesage.js': { + comment: 'SharedWorker tests are not applicable to workerd', + omittedTests: true, + }, + 'shared-worker/eventsource-onopen.js': { + comment: 'SharedWorker tests are not applicable to workerd', + omittedTests: true, + }, + 'shared-worker/eventsource-prototype.js': { + comment: 'SharedWorker tests are not applicable to workerd', + omittedTests: true, + }, + 'shared-worker/eventsource-url.js': { + comment: 'SharedWorker tests are not applicable to workerd', + omittedTests: true, + }, +} satisfies TestRunnerConfig; diff --git a/src/wpt/harness/assertions.ts b/src/wpt/harness/assertions.ts index 2359ea47d35..744ce327269 100644 --- a/src/wpt/harness/assertions.ts +++ b/src/wpt/harness/assertions.ts @@ -81,9 +81,14 @@ declare global { maybeDescription?: string ): Promise; + function assert_own_property( + object: object, + property_name: string | symbol, + description?: string + ): void; function assert_not_own_property( object: object, - property_name: string, + property_name: string | symbol, description?: string ): void; function promise_rejects_js( @@ -103,6 +108,24 @@ declare global { description?: string ): void; + function assert_greater_than_equal( + actual: number, + expected: number, + description?: string + ): void; + + function assert_less_than( + actual: number, + expected: number, + description?: string + ): void; + + function assert_less_than_equal( + actual: number, + expected: number, + description?: string + ): void; + function promise_rejects_exactly( test: Test, exception: typeof Error, @@ -421,6 +444,21 @@ globalThis.promise_rejects_dom = ( }); }; +/** + * Assert that ``object`` has an own property with name ``property_name``. + * + * @param object - Object that should have the given property. + * @param property_name - Property name to test. + * @param [description] - Description of the condition being tested. + */ +globalThis.assert_own_property = (object, property_name, description): void => { + ok( + Object.prototype.hasOwnProperty.call(object, property_name), + `expected property ${String(property_name)} missing on object: ` + + (description ?? '') + ); +}; + /** * Assert that ``object`` does not have an own property with name ``property_name``. * @@ -435,7 +473,7 @@ globalThis.assert_not_own_property = ( ): void => { ok( !Object.prototype.hasOwnProperty.call(object, property_name), - `unexpected property ${property_name} is found on object: ` + + `unexpected property ${String(property_name)} is found on object: ` + (description ?? '') ); }; @@ -481,6 +519,43 @@ globalThis.assert_greater_than = (actual, expected, description): void => { ok(actual > expected, description); }; +/** + * Assert that ``actual`` is a number greater than or equal to ``expected``. + * + * @param actual - Test value. + * @param expected - Number that ``actual`` must be greater than or equal to. + * @param [description] - Description of the condition being tested. + */ +globalThis.assert_greater_than_equal = ( + actual, + expected, + description +): void => { + ok(actual >= expected, description); +}; + +/** + * Assert that ``actual`` is a number less than ``expected``. + * + * @param actual - Test value. + * @param expected - Number that ``actual`` must be less than. + * @param [description] - Description of the condition being tested. + */ +globalThis.assert_less_than = (actual, expected, description): void => { + ok(actual < expected, description); +}; + +/** + * Assert that ``actual`` is a number less than or equal to ``expected``. + * + * @param actual - Test value. + * @param expected - Number that ``actual`` must be less than or equal to. + * @param [description] - Description of the condition being tested. + */ +globalThis.assert_less_than_equal = (actual, expected, description): void => { + ok(actual <= expected, description); +}; + /** * Assert that a Promise is rejected with the provided value. * diff --git a/src/wpt/harness/common.ts b/src/wpt/harness/common.ts index 1f130a15882..82d420d3fe3 100644 --- a/src/wpt/harness/common.ts +++ b/src/wpt/harness/common.ts @@ -142,6 +142,8 @@ export type HostInfo = { HTTPS_REMOTE_ORIGIN: string; HTTP_PORT: string; HTTPS_PORT: string; + WS_PORT: string; + WSS_PORT: string; }; export function getHostInfo(): HostInfo { @@ -163,6 +165,8 @@ export function getHostInfo(): HostInfo { HTTPS_REMOTE_ORIGIN: httpsUrl.origin, HTTP_PORT: httpUrl.port, HTTPS_PORT: httpsUrl.port, + WS_PORT: (globalThis.state.env.WS_PORT as string | undefined) ?? '', + WSS_PORT: (globalThis.state.env.WSS_PORT as string | undefined) ?? '', }; } diff --git a/src/wpt/harness/globals.ts b/src/wpt/harness/globals.ts index 0b2c9d24018..58ddcf25786 100644 --- a/src/wpt/harness/globals.ts +++ b/src/wpt/harness/globals.ts @@ -29,6 +29,15 @@ import { getBindingPath } from './common'; // @ts-expect-error We're just exposing enough stuff for the tests to pass; it's not a perfect match globalThis.self = globalThis; +// WPT tests use self.URL which requires URL to be an own property of globalThis +globalThis.URL = URL; + +// Some WPT tests reference addEventListener at the top level during file evaluation. +// workerd doesn't have addEventListener on the global (it uses module exports instead), +// so we provide a no-op stub to allow test files to load without throwing. +// Tests that actually need addEventListener functionality should be marked as omitted. +globalThis.addEventListener = (): void => {}; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access -- We're just exposing enough stuff for the tests to pass; it's not a perfect match globalThis.Window = Object.getPrototypeOf(globalThis).constructor; diff --git a/src/wpt/harness/harness.ts b/src/wpt/harness/harness.ts index 8105e480944..b2b3bf2ac26 100644 --- a/src/wpt/harness/harness.ts +++ b/src/wpt/harness/harness.ts @@ -302,10 +302,14 @@ function replaceInterpolation(code: string): string { const hostInfo = getHostInfo(); return code - .replace('{{host}}', hostInfo.REMOTE_HOST) - .replace('{{ports[http][0]}}', hostInfo.HTTP_PORT) - .replace('{{ports[http][1]}}', hostInfo.HTTP_PORT) - .replace('{{ports[https][0]}}', hostInfo.HTTPS_PORT); + .replace(/\{\{host\}\}/g, hostInfo.REMOTE_HOST) + .replace(/\{\{hosts\[alt\]\[www\]\}\}/g, hostInfo.REMOTE_HOST) + .replace(/\{\{ports\[http\]\[0\]\}\}/g, hostInfo.HTTP_PORT) + .replace(/\{\{ports\[http\]\[1\]\}\}/g, hostInfo.HTTP_PORT) + .replace(/\{\{ports\[https\]\[0\]\}\}/g, hostInfo.HTTPS_PORT) + .replace(/\{\{ports\[ws\]\[0\]\}\}/g, hostInfo.WS_PORT) + .replace(/\{\{ports\[wss\]\[0\]\}\}/g, hostInfo.WSS_PORT) + .replace(/\{\{ports\[h2\]\[0\]\}\}/g, hostInfo.HTTPS_PORT); } function getCodeAtPath( diff --git a/src/wpt/harness/test.ts b/src/wpt/harness/test.ts index cb4c632ad4d..293cc682015 100644 --- a/src/wpt/harness/test.ts +++ b/src/wpt/harness/test.ts @@ -36,7 +36,11 @@ declare global { name?: string, properties?: unknown ): void; - function async_test(func: TestFn, name?: string, properties?: unknown): void; + function async_test( + func: TestFn | string, + name?: string, + properties?: unknown + ): Test; function test(func: TestFn, name?: string, properties?: unknown): void; } @@ -293,15 +297,38 @@ class AsyncTest extends Test { } } -globalThis.async_test = (func, name, properties): void => { - if (maybeAddSkippedTest(name ?? '')) { - return; +globalThis.async_test = (func, name, properties): Test => { + // async_test can be called in two ways: + // 1. async_test(func, name, properties) - func is a TestFn + // 2. async_test(name, properties) - just creates a test with the given name + let testName: string; + let testFunc: TestFn | undefined; + + if (typeof func === 'string') { + // async_test(name, properties) signature + testName = func; + testFunc = undefined; + // name parameter is actually properties in this case + properties = name; + } else { + // async_test(func, name, properties) signature + testName = name ?? ''; + testFunc = func; } - const testCase = new AsyncTest(name ?? '', properties); + if (maybeAddSkippedTest(testName)) { + // Return a dummy test object for skipped tests + return new SkippedTest(testName, 'DISABLED'); + } + + const testCase = new AsyncTest(testName, properties); globalThis.state.subtests.push(testCase); - testCase.step(func, testCase, testCase); + if (testFunc) { + testCase.step(testFunc, testCase, testCase); + } + + return testCase; }; /** diff --git a/src/wpt/webidl-test.ts b/src/wpt/webidl-test.ts new file mode 100644 index 00000000000..557a325498a --- /dev/null +++ b/src/wpt/webidl-test.ts @@ -0,0 +1,98 @@ +// Copyright (c) 2017-2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +import { type TestRunnerConfig } from 'harness/harness'; + +export default { + 'ecmascript-binding/builtin-function-properties.any.js': {}, + 'ecmascript-binding/class-string-interface.any.js': { + comment: '@@toStringTag property descriptor mismatch', + expectedFailures: [ + '@@toStringTag exists on the prototype with the appropriate descriptor', + ], + }, + 'ecmascript-binding/class-string-iterator-prototype-object.any.js': { + comment: 'Iterator @@toStringTag is different in workerd', + expectedFailures: [ + '@@toStringTag exists with the appropriate descriptor', + 'Object.prototype.toString', + 'Object.prototype.toString applied to a null-prototype instance', + ], + }, + 'ecmascript-binding/class-string-named-properties-object.window.js': { + comment: 'Window-specific test', + omittedTests: true, + }, + 'ecmascript-binding/es-exceptions/DOMException-constants.any.js': { + comment: 'DOMException constants have wrong configurable descriptor', + expectedFailures: true, + }, + 'ecmascript-binding/es-exceptions/DOMException-constructor-and-prototype.any.js': + { + comment: 'DOMException property descriptors differ from spec', + expectedFailures: true, + }, + 'ecmascript-binding/es-exceptions/DOMException-constructor-behavior.any.js': { + comment: 'DOMException property inheritance differs from spec', + expectedFailures: true, + }, + 'ecmascript-binding/es-exceptions/DOMException-custom-bindings.any.js': { + comment: 'DOMException property descriptors differ from spec', + expectedFailures: true, + }, + 'ecmascript-binding/es-exceptions/DOMException-is-error.any.js': { + comment: 'DOMException.prototype not in Error.prototype chain', + expectedFailures: [''], + }, + 'ecmascript-binding/global-immutable-prototype.any.js': { + comment: 'globalThis prototype is unconfigurable in workerd', + expectedFailures: true, + }, + 'ecmascript-binding/global-mutable-prototype.any.js': { + comment: 'globalThis prototype is unconfigurable in workerd', + expectedFailures: true, + }, + 'ecmascript-binding/global-object-implicit-this-value.any.js': { + comment: + 'Script fails to load - addEventListener is not defined in workerd', + expectedFailures: [ + "Global object's getter throws when called on incompatible object", + "Global object's setter throws when called on incompatible object", + "Global object's operation throws when called on incompatible object", + "Global object's getter works when called on null / undefined", + "Global object's setter works when called on null / undefined", + "Global object's operation works when called on null / undefined", + ], + }, + 'ecmascript-binding/legacy-factor-function-subclass.window.js': { + comment: 'Window-specific test', + omittedTests: true, + }, + 'ecmascript-binding/legacy-factory-function-builtin-properties.window.js': { + comment: 'Window-specific test', + omittedTests: true, + }, + 'ecmascript-binding/legacy-platform-object/helper.js': {}, + 'ecmascript-binding/no-regexp-special-casing.any.js': { + comment: 'self.addEventListener is not available in workerd', + expectedFailures: [ + 'Conversion to a dictionary works', + 'Conversion to a sequence works', + 'Can be used as an object implementing a callback interface', + ], + }, + 'ecmascript-binding/observable-array-no-leak-of-internals.window.js': { + comment: 'Window-specific test', + omittedTests: true, + }, + 'ecmascript-binding/observable-array-ownkeys.window.js': { + comment: 'Window-specific test', + omittedTests: true, + }, + 'ecmascript-binding/support/create-realm.js': {}, + 'idlharness.any.js': { + comment: 'Missing /resources/WebIDLParser.js resource file', + omittedTests: true, + }, +} satisfies TestRunnerConfig; diff --git a/src/wpt/websockets-test.ts b/src/wpt/websockets-test.ts new file mode 100644 index 00000000000..998b38282fa --- /dev/null +++ b/src/wpt/websockets-test.ts @@ -0,0 +1,406 @@ +// Copyright (c) 2017-2026 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 + +import { type TestRunnerConfig } from 'harness/harness'; + +// The WPT WebSocket tests use addEventListener with useCapture=true, which workerd +// doesn't support. This function removes the useCapture argument from those calls. +// Pattern: addEventListener('event', handler, true) -> addEventListener('event', handler) +function removeUseCapture(code: string): string { + return code.replace(/,\s*true\s*\)/g, ')'); +} + +export default { + 'Close-1000-reason.any.js': { + replace: removeUseCapture, + }, + 'Close-1000-verify-code.any.js': { + replace: removeUseCapture, + }, + 'Close-1000.any.js': { + replace: removeUseCapture, + }, + 'Close-1005-verify-code.any.js': { + replace: removeUseCapture, + }, + 'Close-1005.any.js': { + comment: + 'workerd throws TypeError instead of InvalidAccessError for close(1005), test hangs', + disabledTests: true, + }, + 'Close-2999-reason.any.js': { + comment: 'workerd does not throw INVALID_ACCESS_ERR for close(2999)', + expectedFailures: [ + 'Create WebSocket - Close the Connection - close(2999, reason) - INVALID_ACCESS_ERR is thrown', + ], + replace: removeUseCapture, + }, + 'Close-3000-reason.any.js': { + replace: removeUseCapture, + }, + 'Close-3000-verify-code.any.js': { + replace: removeUseCapture, + }, + 'Close-4999-reason.any.js': { + replace: removeUseCapture, + }, + 'Close-Reason-124Bytes.any.js': { + comment: 'workerd does not throw SYNTAX_ERR for reason > 123 bytes', + expectedFailures: [ + "Create WebSocket - Close the Connection - close(code, 'reason more than 123 bytes') - SYNTAX_ERR is thrown", + ], + replace: removeUseCapture, + }, + 'Close-delayed.any.js': { + replace: removeUseCapture, + }, + 'Close-onlyReason.any.js': { + comment: + 'workerd throws TypeError instead of INVALID_ACCESS_ERR for close(undefined, reason), test hangs', + disabledTests: true, + }, + 'Close-readyState-Closed.any.js': { + replace: removeUseCapture, + }, + 'Close-readyState-Closing.any.js': { + replace: removeUseCapture, + }, + 'Close-reason-unpaired-surrogates.any.js': { + comment: 'workerd handles unpaired surrogates differently', + expectedFailures: [ + 'Create WebSocket - Close the Connection - close(reason with unpaired surrogates) - connection should get closed', + ], + replace: removeUseCapture, + }, + 'Close-server-initiated-close.any.js': { + comment: + 'readyState is CLOSING (2) instead of CLOSED (3) when close event fires', + expectedFailures: [ + 'Create WebSocket - Server initiated Close - Client sends back a CLOSE - readyState should be in CLOSED state and wasClean is TRUE - Connection should be closed', + ], + replace: removeUseCapture, + }, + 'Close-undefined.any.js': { + replace: removeUseCapture, + }, + 'Create-asciiSep-protocol-string.any.js': { + comment: 'Error name is SyntaxError instead of SYNTAX_ERR', + expectedFailures: [ + 'Create WebSocket - Pass a valid URL and a protocol string with an ascii separator character - SYNTAX_ERR is thrown', + ], + }, + 'Create-blocked-port.any.js': { + comment: 'Port blocking works differently in workerd', + disabledTests: true, + }, + 'Create-extensions-empty.any.js': { + comment: 'workerd WebSocket.extensions is null instead of empty string', + expectedFailures: [ + "Create WebSocket - wsocket.extensions should be set to '' after connection is established - Connection should be closed", + ], + replace: removeUseCapture, + }, + 'Create-http-urls.any.js': { + comment: 'workerd requires ws/wss scheme, not http/https', + expectedFailures: ['WebSocket: ensure both HTTP schemes are supported'], + }, + 'Create-invalid-urls.any.js': {}, + 'Create-non-absolute-url.any.js': { + comment: 'workerd throws SyntaxError for non-absolute URLs', + expectedFailures: [ + 'Create WebSocket - Pass a non absolute URL: test', + 'Create WebSocket - Pass a non absolute URL: ?', + 'Create WebSocket - Pass a non absolute URL: null', + 'Create WebSocket - Pass a non absolute URL: 123', + ], + }, + 'Create-nonAscii-protocol-string.any.js': { + comment: 'Error name is SyntaxError instead of SYNTAX_ERR', + expectedFailures: [ + 'Create WebSocket - Pass a valid URL and a protocol string with non-ascii values - SYNTAX_ERR is thrown', + ], + }, + 'Create-on-worker-shutdown.any.js': { + comment: 'Worker shutdown behavior is different in workerd', + disabledTests: true, + }, + 'Create-protocol-with-space.any.js': { + comment: 'Error name is SyntaxError instead of SYNTAX_ERR', + expectedFailures: [ + 'Create WebSocket - Pass a valid URL and a protocol string with a space in it - SYNTAX_ERR is thrown', + ], + }, + 'Create-protocols-repeated-case-insensitive.any.js': { + comment: + 'workerd does not throw for case-insensitive duplicate protocols (allows "Echo" and "echo")', + expectedFailures: [ + 'Create WebSocket - Pass a valid URL and an array of protocol strings with repeated values but different case - SYNTAX_ERR is thrown', + ], + }, + 'Create-protocols-repeated.any.js': { + comment: 'Error name is SyntaxError instead of SYNTAX_ERR', + expectedFailures: [ + 'Create WebSocket - Pass a valid URL and an array of protocol strings with repeated values - SYNTAX_ERR is thrown', + ], + }, + 'Create-url-with-space.any.js': { + comment: 'Error name is SyntaxError instead of SYNTAX_ERR', + expectedFailures: [ + 'Create WebSocket - Pass a URL with a space - SYNTAX_ERR should be thrown', + ], + }, + 'Create-valid-url-array-protocols.any.js': { + replace: removeUseCapture, + }, + 'Create-valid-url-binaryType-blob.any.js': { + comment: + 'workerd WebSocket binaryType defaults to "arraybuffer", not "blob"; test hangs', + disabledTests: true, + }, + 'Create-valid-url-protocol-empty.any.js': { + replace: removeUseCapture, + }, + 'Create-valid-url-protocol-setCorrectly.any.js': { + replace: removeUseCapture, + }, + 'Create-valid-url-protocol-string.any.js': { + replace: removeUseCapture, + }, + 'Create-valid-url-protocol.any.js': { + replace: removeUseCapture, + }, + 'Create-valid-url.any.js': { + replace: removeUseCapture, + }, + 'Send-0byte-data.any.js': { + comment: + 'workerd returns 0 instead of undefined for empty message data; test hangs', + disabledTests: true, + }, + 'Send-65K-data.any.js': { + comment: + 'workerd returns byte count instead of undefined for bufferedAmount; test hangs', + disabledTests: true, + }, + 'Send-before-open.any.js': { + comment: 'Error name is InvalidStateError instead of INVALID_STATE_ERR', + expectedFailures: [ + 'Send data on a WebSocket before connection is opened - INVALID_STATE_ERR is returned', + ], + }, + // The following Send tests all check bufferedAmount which returns byte count in workerd + // instead of undefined as expected by the spec. They also hang after failures. + 'Send-binary-65K-arraybuffer.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-binary-arraybuffer.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-binary-arraybufferview-float16.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-binary-arraybufferview-float32.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-binary-arraybufferview-float64.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-binary-arraybufferview-int16-offset.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-binary-arraybufferview-int32.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-binary-arraybufferview-int8.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-binary-arraybufferview-uint16-offset-length.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-binary-arraybufferview-uint32-offset.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-binary-arraybufferview-uint8-offset-length.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-binary-arraybufferview-uint8-offset.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-binary-blob.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-data.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-data.worker.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-null.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-paired-surrogates.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-unicode-data.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'Send-unpaired-surrogates.any.js': { + comment: + 'bufferedAmount returns byte count instead of undefined; test hangs', + disabledTests: true, + }, + 'back-forward-cache-with-closed-websocket-connection-ccns.tentative.window.js': + { + comment: 'Back/forward cache tests are browser-specific', + omittedTests: true, + }, + 'back-forward-cache-with-closed-websocket-connection.window.js': { + comment: 'Back/forward cache tests are browser-specific', + omittedTests: true, + }, + 'back-forward-cache-with-open-websocket-connection-ccns.tentative.window.js': + { + comment: 'Back/forward cache tests are browser-specific', + omittedTests: true, + }, + 'back-forward-cache-with-open-websocket-connection.window.js': { + comment: 'Back/forward cache tests are browser-specific', + omittedTests: true, + }, + 'basic-auth.any.js': { + comment: 'Basic authentication for WebSockets is not supported', + disabledTests: true, + }, + 'binaryType-wrong-value.any.js': { + comment: 'binaryType is undefined instead of blob in workerd; test hangs', + disabledTests: true, + }, + 'bufferedAmount-unchanged-by-sync-xhr.any.js': { + comment: 'Synchronous XHR is not supported in Workers', + omittedTests: true, + }, + 'close-invalid.any.js': { + comment: 'workerd does not throw errors for invalid close codes', + expectedFailures: [ + '0 on a websocket', + '500 on a websocket', + 'NaN on a websocket', + 'String on a websocket', + 'null on a websocket', + '2**16+1000 on a websocket', + ], + replace: removeUseCapture, + }, + 'constants.sub.js': {}, + 'constructor.any.js': {}, + 'cookies/support/websocket-cookies-helper.sub.js': { + comment: 'Cookie support helper, not an actual test', + omittedTests: true, + }, + 'eventhandlers.any.js': { + comment: 'TreatNonCallableAsNull behavior differs from spec', + expectedFailures: [ + 'Event handler for open should have [TreatNonCallableAsNull]', + 'Event handler for error should have [TreatNonCallableAsNull]', + 'Event handler for close should have [TreatNonCallableAsNull]', + 'Event handler for message should have [TreatNonCallableAsNull]', + ], + }, + 'idlharness.any.js': { + comment: + 'Test file /resources/WebIDLParser.js not found. Update wpt_test.bzl to handle this case.', + omittedTests: true, + }, + 'interfaces/WebSocket/close/close-connecting-async.any.js': { + comment: + 'readyState is CONNECTING (1) instead of CLOSING (2) after close()', + expectedFailures: [ + 'close event should be fired asynchronously when WebSocket is connecting', + ], + }, + 'mixed-content.https.any.js': { + comment: 'Mixed content checks are browser-specific', + omittedTests: true, + }, + 'opening-handshake/003-sets-origin.worker.js': { + comment: 'importScripts is not available in Workers', + omittedTests: true, + }, + 'referrer.any.js': { + comment: 'Referrer behavior is different in workers', + disabledTests: true, + }, + 'remove-own-iframe-during-onerror.window.js': { + comment: 'iframe tests are browser-specific', + omittedTests: true, + }, + 'send-many-64K-messages-with-backpressure.any.js': { + replace: removeUseCapture, + }, + // WebSocketStream is a tentative API and not yet implemented + 'stream/tentative/abort.any.js': { + comment: 'WebSocketStream is not yet implemented', + omittedTests: true, + }, + 'stream/tentative/backpressure-receive.any.js': { + comment: 'WebSocketStream is not yet implemented', + omittedTests: true, + }, + 'stream/tentative/backpressure-send.any.js': { + comment: 'WebSocketStream is not yet implemented', + omittedTests: true, + }, + 'stream/tentative/close.any.js': { + comment: 'WebSocketStream is not yet implemented', + omittedTests: true, + }, + 'stream/tentative/constructor.any.js': { + comment: 'WebSocketStream is not yet implemented', + omittedTests: true, + }, + 'stream/tentative/remote-close.any.js': { + comment: 'WebSocketStream is not yet implemented', + omittedTests: true, + }, + 'stream/tentative/websocket-error.any.js': { + comment: 'WebSocketStream is not yet implemented', + omittedTests: true, + }, +} satisfies TestRunnerConfig;