diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/floats/hit-test-floats-005.html.ini b/tests/wpt/metadata-layout-2020/css/CSS2/floats/hit-test-floats-005.html.ini deleted file mode 100644 index baa9f1a7541..00000000000 --- a/tests/wpt/metadata-layout-2020/css/CSS2/floats/hit-test-floats-005.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[hit-test-floats-005.html] - [Miss clipped float] - expected: FAIL - diff --git a/tests/wpt/metadata-layout-2020/css/css-flexbox/image-as-flexitem-size-003.html.ini b/tests/wpt/metadata-layout-2020/css/css-flexbox/image-as-flexitem-size-003.html.ini index ce4f7756978..eaa0ec8177f 100644 --- a/tests/wpt/metadata-layout-2020/css/css-flexbox/image-as-flexitem-size-003.html.ini +++ b/tests/wpt/metadata-layout-2020/css/css-flexbox/image-as-flexitem-size-003.html.ini @@ -2,12 +2,6 @@ [.flexbox > img 1] expected: FAIL - [.flexbox > img 2] - expected: FAIL - - [.flexbox > img 3] - expected: FAIL - [.flexbox > img 5] expected: FAIL @@ -20,24 +14,9 @@ [.flexbox > img 8] expected: FAIL - [.flexbox > img 9] - expected: FAIL - - [.flexbox > img 14] - expected: FAIL - - [.flexbox > img 15] - expected: FAIL - [.flexbox > img 16] expected: FAIL - [.flexbox > img 17] - expected: FAIL - - [.flexbox > img 18] - expected: FAIL - [.flexbox > img 11] expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/css-flexbox/image-as-flexitem-size-003v.html.ini b/tests/wpt/metadata-layout-2020/css/css-flexbox/image-as-flexitem-size-003v.html.ini index 26de3019b19..67fd0e3c27e 100644 --- a/tests/wpt/metadata-layout-2020/css/css-flexbox/image-as-flexitem-size-003v.html.ini +++ b/tests/wpt/metadata-layout-2020/css/css-flexbox/image-as-flexitem-size-003v.html.ini @@ -2,12 +2,6 @@ [.flexbox > img 1] expected: FAIL - [.flexbox > img 2] - expected: FAIL - - [.flexbox > img 3] - expected: FAIL - [.flexbox > img 5] expected: FAIL @@ -20,24 +14,9 @@ [.flexbox > img 8] expected: FAIL - [.flexbox > img 9] - expected: FAIL - - [.flexbox > img 14] - expected: FAIL - - [.flexbox > img 15] - expected: FAIL - [.flexbox > img 16] expected: FAIL - [.flexbox > img 17] - expected: FAIL - - [.flexbox > img 18] - expected: FAIL - [.flexbox > img 11] expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/css-transforms/transform-scale-hittest.html.ini b/tests/wpt/metadata-layout-2020/css/css-transforms/transform-scale-hittest.html.ini index 4a1e8110f6f..f8e7e539aae 100644 --- a/tests/wpt/metadata-layout-2020/css/css-transforms/transform-scale-hittest.html.ini +++ b/tests/wpt/metadata-layout-2020/css/css-transforms/transform-scale-hittest.html.ini @@ -2,3 +2,6 @@ [Hit test intersecting scaled box] expected: FAIL + [Hit test within unscaled box] + expected: FAIL + diff --git a/tests/wpt/metadata-layout-2020/css/cssom-view/elementFromPoint-001.html.ini b/tests/wpt/metadata-layout-2020/css/cssom-view/elementFromPoint-001.html.ini deleted file mode 100644 index e38782d8c85..00000000000 --- a/tests/wpt/metadata-layout-2020/css/cssom-view/elementFromPoint-001.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[elementFromPoint-001.html] - [CSSOM View - 5 - extensions to the Document interface] - expected: FAIL - diff --git a/tests/wpt/metadata-layout-2020/css/cssom-view/elementsFromPoint-invalid-cases.html.ini b/tests/wpt/metadata-layout-2020/css/cssom-view/elementsFromPoint-invalid-cases.html.ini new file mode 100644 index 00000000000..e181af5397f --- /dev/null +++ b/tests/wpt/metadata-layout-2020/css/cssom-view/elementsFromPoint-invalid-cases.html.ini @@ -0,0 +1,4 @@ +[elementsFromPoint-invalid-cases.html] + [The root element is the last element returned for otherwise empty queries within the viewport] + expected: FAIL + diff --git a/tests/wpt/metadata-layout-2020/css/cssom-view/matchMedia-display-none-iframe.html.ini b/tests/wpt/metadata-layout-2020/css/cssom-view/matchMedia-display-none-iframe.html.ini deleted file mode 100644 index e6e1f29e274..00000000000 --- a/tests/wpt/metadata-layout-2020/css/cssom-view/matchMedia-display-none-iframe.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[matchMedia-display-none-iframe.html] - expected: ERROR diff --git a/tests/wpt/metadata-layout-2020/custom-elements/reactions/HTMLMediaElement.html.ini b/tests/wpt/metadata-layout-2020/custom-elements/reactions/HTMLMediaElement.html.ini deleted file mode 100644 index 2ca05f57bb0..00000000000 --- a/tests/wpt/metadata-layout-2020/custom-elements/reactions/HTMLMediaElement.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[HTMLMediaElement.html] - expected: TIMEOUT diff --git a/tests/wpt/metadata-layout-2020/fetch/content-type/response.window.js.ini b/tests/wpt/metadata-layout-2020/fetch/content-type/response.window.js.ini index ee975808986..a22f93d64f1 100644 --- a/tests/wpt/metadata-layout-2020/fetch/content-type/response.window.js.ini +++ b/tests/wpt/metadata-layout-2020/fetch/content-type/response.window.js.ini @@ -312,21 +312,24 @@ [Response: combined response Content-Type: text/html;" \\" text/plain ";charset=GBK] expected: NOTRUN - [ + + diff --git a/tests/wpt/web-platform-tests/infrastructure/testdriver/click_nested.html b/tests/wpt/web-platform-tests/infrastructure/testdriver/click_nested.html new file mode 100644 index 00000000000..378b9e8c0f1 --- /dev/null +++ b/tests/wpt/web-platform-tests/infrastructure/testdriver/click_nested.html @@ -0,0 +1,31 @@ + + +TestDriver click method with multiple windows and nested iframe + + + + + + + + diff --git a/tests/wpt/web-platform-tests/infrastructure/testdriver/click_outer_child.html b/tests/wpt/web-platform-tests/infrastructure/testdriver/click_outer_child.html new file mode 100644 index 00000000000..ae4944635f0 --- /dev/null +++ b/tests/wpt/web-platform-tests/infrastructure/testdriver/click_outer_child.html @@ -0,0 +1,4 @@ + + + + diff --git a/tests/wpt/web-platform-tests/infrastructure/testdriver/click_window.html b/tests/wpt/web-platform-tests/infrastructure/testdriver/click_window.html new file mode 100644 index 00000000000..614a92478e0 --- /dev/null +++ b/tests/wpt/web-platform-tests/infrastructure/testdriver/click_window.html @@ -0,0 +1,24 @@ + + +TestDriver click method in window + + + + + + diff --git a/tests/wpt/web-platform-tests/input-events/input-events-get-target-ranges-backspace.tentative.html b/tests/wpt/web-platform-tests/input-events/input-events-get-target-ranges-backspace.tentative.html index 6967fa746c6..50d5cb96e84 100644 --- a/tests/wpt/web-platform-tests/input-events/input-events-get-target-ranges-backspace.tentative.html +++ b/tests/wpt/web-platform-tests/input-events/input-events-get-target-ranges-backspace.tentative.html @@ -163,6 +163,21 @@ function checkBeforeinputAndInputEventsOnNOOP() { "input event shouldn't be fired when the key operation does not cause modifying the DOM tree"); } +promise_test(async () => { + reset(); + editor.innerHTML = "

abc

"; + selection.collapse(editor.firstChild.firstChild, 0); + await sendBackspaceKey(); + assert_equals(editor.innerHTML, "

abc

"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: editor.firstChild.firstChild, + startOffset: 0, + endContainer: editor.firstChild.firstChild, + endOffset: 0, + }); + checkGetTargetRangesOfInputOnDoNothing(); +}, 'Backspace at "

[]abc

"'); + // Simply deletes the previous ASCII character of caret position. promise_test(async () => { reset(); diff --git a/tests/wpt/web-platform-tests/input-events/input-events-get-target-ranges-forwarddelete.tentative.html b/tests/wpt/web-platform-tests/input-events/input-events-get-target-ranges-forwarddelete.tentative.html index dd349fc444d..03518d54e42 100644 --- a/tests/wpt/web-platform-tests/input-events/input-events-get-target-ranges-forwarddelete.tentative.html +++ b/tests/wpt/web-platform-tests/input-events/input-events-get-target-ranges-forwarddelete.tentative.html @@ -211,6 +211,54 @@ promise_test(async () => { checkGetTargetRangesOfInputOnDeleteSomething(); }, 'Delete at "

[]abc

"'); +promise_test(async () => { + reset(); + editor.innerHTML = "

abc

"; + let abc = editor.querySelector("p").firstChild; + selection.collapse(abc, 3); + await sendDeleteKey(); + assert_equals(editor.innerHTML, "

abc

"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: abc, + startOffset: 3, + endContainer: abc, + endOffset: 3, + }); + checkGetTargetRangesOfInputOnDoNothing(); +}, 'Delete at "

abc[]

"'); + +promise_test(async () => { + reset(); + editor.innerHTML = "

abc

"; + let abc = editor.querySelector("p").firstChild; + selection.collapse(abc, 3); + await sendDeleteKey(); + assert_equals(editor.innerHTML, "

abc

"); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: abc, + startOffset: 3, + endContainer: abc, + endOffset: 3, + }); + checkGetTargetRangesOfInputOnDoNothing(); +}, 'Delete at "

abc[]

"'); + +promise_test(async () => { + reset(); + editor.innerHTML = `


`; + let p = editor.querySelector("p"); + selection.collapse(p, 1); + await sendDeleteKey(); + assert_equals(editor.innerHTML, `


`); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: p, + endOffset: 1, + }); + checkGetTargetRangesOfInputOnDoNothing(); +}, 'Delete at "

[]

"'); + // Should delete the `` element because it becomes empty. // However, we need discussion whether the `` element should be // contained by a range of `getTargetRanges()`. @@ -566,6 +614,25 @@ promise_test(async () => { checkGetTargetRangesOfInputOnDeleteSomething(); }, 'Delete at "

abc[]

def

"'); +promise_test(async () => { + reset(); + editor.innerHTML = `


def

`; + let p1 = editor.firstChild; + let img = p1.firstChild; + let p2 = p1.nextSibling; + let def = p2.firstChild; + selection.collapse(p1, 1); + await sendDeleteKey(); + assert_equals(editor.innerHTML, `

def

`); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p1, + startOffset: 1, + endContainer: def, + endOffset: 0, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, `Delete at "

{}

def

"`); + promise_test(async () => { reset(); editor.innerHTML = "

abc

def

"; @@ -941,6 +1008,23 @@ promise_test(async () => { checkGetTargetRangesOfInputOnDeleteSomething(); }, 'Delete at "
abc[]

def
"'); +promise_test(async () => { + reset(); + editor.innerHTML = `


def
`; + let div = editor.querySelector("div"); + let img = div.firstChild; + selection.collapse(div, 1); + await sendDeleteKey(); + assert_equals(editor.innerHTML, `
def
`); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: div, + startOffset: 1, + endContainer: div, + endOffset: 3, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "
{}

def
"'); + promise_test(async () => { reset(); editor.innerHTML = `
abc
def
`; @@ -1069,6 +1153,46 @@ promise_test(async () => { checkGetTargetRangesOfInputOnDeleteSomething(); }, 'Delete at "
abc[

]def
ghi

"'); +promise_test(async () => { + reset(); + editor.innerHTML = "
abc

def
ghi

"; + let div = editor.firstChild; + let p = editor.querySelector("p"); + let def = p.firstChild; + let abc = div.firstChild; + selection.collapse(abc, 3); + await sendDeleteKey(); + assert_in_array(editor.innerHTML, ["
abcdef

ghi

", + "
abcdef

ghi

"]); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: div, + startOffset: 1, + endContainer: def, + endOffset: 0, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "
abc[]

def
ghi

"'); + +promise_test(async () => { + reset(); + editor.innerHTML = `

def
ghi

`; + let div = editor.firstChild; + let p = editor.querySelector("p"); + let def = p.firstChild; + let abc = div.firstChild; + selection.collapse(div, 1); + await sendDeleteKey(); + assert_in_array(editor.innerHTML, [`
def

ghi

`, + `
def

ghi

`]); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: div, + startOffset: 1, + endContainer: def, + endOffset: 0, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "
{}

def
ghi

"'); + // Joining parent block and child block should remove invisible preceding // white-spaces of the child block and invisible leading white-spaces in // the child block, and they should be contained in a range of @@ -1182,6 +1306,44 @@ promise_test(async () => { checkGetTargetRangesOfInputOnDeleteSomething(); }, 'Delete at "

abc[]

def
"'); +promise_test(async () => { + reset(); + editor.innerHTML = "

abc

def
"; + let p = editor.querySelector("p"); + let abc = p.firstChild; + let def = p.nextSibling; + selection.collapse(abc, 3); + await sendDeleteKey(); + assert_in_array(editor.innerHTML, ["

abcdef

", + "

abcdef

"]); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: def, + endOffset: 0, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "

abc[]

def
"'); + +promise_test(async () => { + reset(); + editor.innerHTML = `


def
`; + let p = editor.querySelector("p"); + let abc = p.firstChild; + let def = p.nextSibling; + selection.collapse(p, 1); + await sendDeleteKey(); + assert_in_array(editor.innerHTML, [`

def

`, + `

def

`]); + checkGetTargetRangesOfBeforeinputOnDeleteSomething({ + startContainer: p, + startOffset: 1, + endContainer: def, + endOffset: 0, + }); + checkGetTargetRangesOfInputOnDeleteSomething(); +}, 'Delete at "

{}

def
"'); + promise_test(async () => { reset(); editor.innerHTML = "

abc

def
"; diff --git a/tests/wpt/web-platform-tests/interfaces/webxr.idl b/tests/wpt/web-platform-tests/interfaces/webxr.idl index 81e42e908cb..c1b7175765d 100644 --- a/tests/wpt/web-platform-tests/interfaces/webxr.idl +++ b/tests/wpt/web-platform-tests/interfaces/webxr.idl @@ -118,6 +118,9 @@ enum XREye { readonly attribute XREye eye; readonly attribute Float32Array projectionMatrix; [SameObject] readonly attribute XRRigidTransform transform; + readonly attribute double? recommendedViewportScale; + + undefined requestViewportScale(double? scale); }; [SecureContext, Exposed=Window] interface XRViewport { diff --git a/tests/wpt/web-platform-tests/lint.ignore b/tests/wpt/web-platform-tests/lint.ignore index c740932e60a..ae6c07b877c 100644 --- a/tests/wpt/web-platform-tests/lint.ignore +++ b/tests/wpt/web-platform-tests/lint.ignore @@ -180,8 +180,6 @@ SET TIMEOUT: media-source/mediasource-util.js SET TIMEOUT: media-source/URL-createObjectURL-revoke.html SET TIMEOUT: mixed-content/generic/sanity-checker.js SET TIMEOUT: navigation-timing/* -SET TIMEOUT: html/canvas/offscreen/the-offscreen-canvas/* -SET TIMEOUT: html/canvas/offscreen/manual/the-offscreen-canvas/* SET TIMEOUT: old-tests/submission/Microsoft/history/history_000.htm SET TIMEOUT: paint-timing/resources/subframe-painting.html SET TIMEOUT: payment-request/allowpayment/setting-allowpaymentrequest-timing.https.sub.html @@ -618,6 +616,8 @@ CSS-COLLIDING-SUPPORT-NAME: css/css-shapes/shape-outside/shape-image/support/ani CSS-COLLIDING-SUPPORT-NAME: css/css-display/support/util.js CSS-COLLIDING-SUPPORT-NAME: css/CSS2/normal-flow/support/replaced-min-max-1.png CSS-COLLIDING-SUPPORT-NAME: css/vendor-imports/mozilla/mozilla-central-reftests/ui3/support/replaced-min-max-1.png +CSS-COLLIDING-REF-NAME: css/css-multicol/baseline-001-ref.html +CSS-COLLIDING-REF-NAME: css/css-grid/subgrid/baseline-001-ref.html CSS-COLLIDING-TEST-NAME: css/css-contain/inheritance.html CSS-COLLIDING-TEST-NAME: css/css-contain/content-visibility/inheritance.html @@ -691,14 +691,9 @@ MISSING-LINK: css/filter-effects/*.any.js # Tests that use WebKit/Blink testing APIs LAYOUTTESTS APIS: import-maps/common/resources/common-test-helper.js -LAYOUTTESTS APIS: resources/test-only-api.js LAYOUTTESTS APIS: resources/chromium/enable-hyperlink-auditing.js LAYOUTTESTS APIS: resources/chromium/generic_sensor_mocks.js -LAYOUTTESTS APIS: resources/chromium/nfc-mock.js LAYOUTTESTS APIS: resources/chromium/webxr-test.js -LAYOUTTESTS APIS: web-nfc/NDEFReader-document-hidden-manual.https.html -LAYOUTTESTS APIS: web-nfc/NDEFReader_scan.https.html -LAYOUTTESTS APIS: web-nfc/NDEFWriter_write.https.html LAYOUTTESTS APIS: webxr/resources/webxr_util.js # Signed Exchange files have hard-coded URLs in the certUrl field @@ -733,6 +728,7 @@ AHEM SYSTEM FONT: acid/acid3/test.html AHEM SYSTEM FONT: resource-timing/resources/all_resource_types.htm AHEM SYSTEM FONT: resource-timing/resources/iframe-reload-TAO.sub.html AHEM SYSTEM FONT: html/canvas/element/drawing-text-to-the-canvas/2d.text.measure.fontBoundingBox.ahem.html +AHEM SYSTEM FONT: css/css-font-loading/fontface-override-descriptors.html # TODO: The following should be deleted along with the Ahem web font cleanup # PR (https://github.com/web-platform-tests/wpt/pull/18702) @@ -744,7 +740,6 @@ TESTHARNESS-IN-OTHER-TYPE: html/canvas/element/manual/wide-gamut-canvas/imagedat TESTHARNESS-IN-OTHER-TYPE: css/CSS2/floats-clear/adjoining-float-new-fc-crash.html TESTHARNESS-IN-OTHER-TYPE: css/CSS2/floats/floats-saturated-position-crash.html TESTHARNESS-IN-OTHER-TYPE: css/CSS2/linebox/video-needs-layout-crash.html -TESTHARNESS-IN-OTHER-TYPE: css/css-animations/keyframes-remove-documentElement-crash.html TESTHARNESS-IN-OTHER-TYPE: css/css-break/break-before-with-no-fragmentation-crash.html TESTHARNESS-IN-OTHER-TYPE: css/css-multicol/abspos-in-multicol-with-spanner-crash.html TESTHARNESS-IN-OTHER-TYPE: css/css-multicol/extremely-tall-multicol-with-extremely-tall-child-crash.html diff --git a/tests/wpt/web-platform-tests/measure-memory/main-frame-and-worker.tentative.window.js b/tests/wpt/web-platform-tests/measure-memory/main-frame-and-worker.tentative.window.js new file mode 100644 index 00000000000..514a4ed0673 --- /dev/null +++ b/tests/wpt/web-platform-tests/measure-memory/main-frame-and-worker.tentative.window.js @@ -0,0 +1,24 @@ +// META: script=/common/get-host-info.sub.js +// META: script=./resources/common.js +// META: timeout=long +'use strict'; + + +promise_test(async testCase => { + try { + const BYTES_PER_WORKER = 10 * 1024 * 1024; + await createWorker(BYTES_PER_WORKER); + const result = await performance.measureMemory(); + assert_greater_than_equal(result.bytes, BYTES_PER_WORKER); + checkMeasureMemory(result, { + allowed: [window.location.href], + required: [window.location.href], + }); + } catch (error) { + if (!(error instanceof DOMException)) { + throw error; + } + assert_equals(error.name, 'SecurityError'); + } +}, 'Well-formed result of performance.measureMemory.'); + diff --git a/tests/wpt/web-platform-tests/measure-memory/resources/common.js b/tests/wpt/web-platform-tests/measure-memory/resources/common.js index 6da98746ecd..3b246719799 100644 --- a/tests/wpt/web-platform-tests/measure-memory/resources/common.js +++ b/tests/wpt/web-platform-tests/measure-memory/resources/common.js @@ -179,4 +179,16 @@ function sameOriginContexts(children) { } } return result; +} + +async function createWorker(bytes) { + const worker = new Worker('resources/worker.js'); + let resolve_promise; + const promise = new Promise(resolve => resolve_promise = resolve); + worker.onmessage = function (message) { + assert_equals(message.data, 'ready'); + resolve_promise(); + } + worker.postMessage({bytes}); + return promise; } \ No newline at end of file diff --git a/tests/wpt/web-platform-tests/measure-memory/resources/worker.js b/tests/wpt/web-platform-tests/measure-memory/resources/worker.js new file mode 100644 index 00000000000..bc194a854cd --- /dev/null +++ b/tests/wpt/web-platform-tests/measure-memory/resources/worker.js @@ -0,0 +1,9 @@ +self.onmessage = function(message) { + const length = message.data.bytes; + self.root = new Uint8Array(length); + // Set some elements to disable potential copy-on-write optimizations. + for (let i = 0; i < length; i += 256) { + self.root[i] = 1; + } + postMessage('ready'); +} diff --git a/tests/wpt/web-platform-tests/resources/test/tests/unit/async-test-return-restrictions.html b/tests/wpt/web-platform-tests/resources/test/tests/unit/async-test-return-restrictions.html new file mode 100644 index 00000000000..0fde2e24223 --- /dev/null +++ b/tests/wpt/web-platform-tests/resources/test/tests/unit/async-test-return-restrictions.html @@ -0,0 +1,135 @@ + + + + + + Restrictions on return value from `async_test` + + + + + diff --git a/tests/wpt/web-platform-tests/resources/test/tests/unit/exceptional-cases.html b/tests/wpt/web-platform-tests/resources/test/tests/unit/exceptional-cases.html index 2be4dacaaec..4054d0311d2 100644 --- a/tests/wpt/web-platform-tests/resources/test/tests/unit/exceptional-cases.html +++ b/tests/wpt/web-platform-tests/resources/test/tests/unit/exceptional-cases.html @@ -127,7 +127,7 @@ promise_test(() => { async_test((t) => { setTimeout(() => { setTimeout(() => t.done(), 0); - async_test((t) => setTimeout(t.done.bind(t), 0), 'after'); + async_test((t) => { setTimeout(t.done.bind(t), 0); }, 'after'); throw new Error('this error is expected'); }, 0); }, 'during'); @@ -205,7 +205,7 @@ if ('onunhandledrejection' in window) { window.addEventListener('unhandledrejection', () => { setTimeout(() => t.done(), 0); - async_test((t) => setTimeout(t.done.bind(t), 0), 'after'); + async_test((t) => { setTimeout(t.done.bind(t), 0); }, 'after'); t.done(); }); }, 'during'); @@ -261,10 +261,10 @@ if ('onunhandledrejection' in window) { return makeTest( () => { setup({ explicit_done: true }); - async_test((t) => setTimeout(t.done.bind(t), 0), 'before'); + async_test((t) => { setTimeout(t.done.bind(t), 0); }, 'before'); Promise.reject(new Error('this error is expected')); window.addEventListener('unhandledrejection', () => { - async_test((t) => setTimeout(t.done.bind(t), 0), 'after'); + async_test((t) => { setTimeout(t.done.bind(t), 0); }, 'after'); done(); }); } diff --git a/tests/wpt/web-platform-tests/resources/test/tests/unit/test-return-restrictions.html b/tests/wpt/web-platform-tests/resources/test/tests/unit/test-return-restrictions.html index b846d4dc430..0295c5214dc 100644 --- a/tests/wpt/web-platform-tests/resources/test/tests/unit/test-return-restrictions.html +++ b/tests/wpt/web-platform-tests/resources/test/tests/unit/test-return-restrictions.html @@ -81,7 +81,7 @@ promise_test(() => { assert_equals(harness.status, 'ERROR'); assert_equals( harness.message, - 'Test named "null" inappropriately returned a value' + 'Test named "null" passed a function to `test` that returned a value.' ); assert_equals(tests.before, 'PASS'); assert_equals(tests.null, 'PASS'); @@ -100,7 +100,7 @@ promise_test(() => { assert_equals(harness.status, 'ERROR'); assert_equals( harness.message, - 'Test named "object" inappropriately returned a value' + 'Test named "object" passed a function to `test` that returned a value.' ); assert_equals(tests.before, 'PASS'); assert_equals(tests.object, 'PASS'); @@ -112,14 +112,15 @@ promise_test(() => { return makeTest( () => { test(() => undefined, 'before'); - test(() => ({ then() {} }), 'thenable'); + test(() => Promise.resolve(5), 'thenable'); test(() => undefined, 'after'); } ).then(({harness, tests}) => { assert_equals(harness.status, 'ERROR'); assert_equals( harness.message, - 'Test named "thenable" inappropriately returned a value, consider using `promise_test` instead' + 'Test named "thenable" passed a function to `test` that returned a value. ' + + 'Consider using `promise_test` instead when using Promises or async/await.' ); assert_equals(tests.before, 'PASS'); assert_equals(tests.thenable, 'PASS'); @@ -143,7 +144,7 @@ promise_test(() => { assert_equals(harness.status, 'ERROR'); assert_equals( harness.message, - 'Test named "restricted" inappropriately returned a value' + 'Test named "restricted" passed a function to `test` that returned a value.' ); assert_equals(tests.before, 'PASS'); assert_equals(tests.restricted, 'PASS'); diff --git a/tests/wpt/web-platform-tests/resources/testdriver.js b/tests/wpt/web-platform-tests/resources/testdriver.js index 4d373c9281d..165147d1430 100644 --- a/tests/wpt/web-platform-tests/resources/testdriver.js +++ b/tests/wpt/web-platform-tests/resources/testdriver.js @@ -15,7 +15,8 @@ } function getPointerInteractablePaintTree(element) { - if (!window.document.contains(element)) { + let elementDocument = element.ownerDocument; + if (!elementDocument.contains(element)) { return []; } @@ -27,10 +28,10 @@ var centerPoint = getInViewCenterPoint(rectangles[0]); - if ("elementsFromPoint" in document) { - return document.elementsFromPoint(centerPoint[0], centerPoint[1]); - } else if ("msElementsFromPoint" in document) { - var rv = document.msElementsFromPoint(centerPoint[0], centerPoint[1]); + if ("elementsFromPoint" in elementDocument) { + return elementDocument.elementsFromPoint(centerPoint[0], centerPoint[1]); + } else if ("msElementsFromPoint" in elementDocument) { + var rv = elementDocument.msElementsFromPoint(centerPoint[0], centerPoint[1]); return Array.prototype.slice.call(rv ? rv : []); } else { throw new Error("document.elementsFromPoint unsupported"); @@ -95,14 +96,6 @@ * the cases the WebDriver command errors */ click: function(element) { - if (window.top !== window) { - return Promise.reject(new Error("can only click in top-level window")); - } - - if (!window.document.contains(element)) { - return Promise.reject(new Error("element in different document or shadow tree")); - } - if (!inView(element)) { element.scrollIntoView({behavior: "instant", block: "end", @@ -135,14 +128,6 @@ * the cases the WebDriver command errors */ send_keys: function(element, keys) { - if (window.top !== window) { - return Promise.reject(new Error("can only send keys in top-level window")); - } - - if (!window.document.contains(element)) { - return Promise.reject(new Error("element in different document or shadow tree")); - } - if (!inView(element)) { element.scrollIntoView({behavior: "instant", block: "end", @@ -176,7 +161,7 @@ /** * Send a sequence of actions * - * This function sends a sequence of actions to the top level window + * This function sends a sequence of actions * to perform. It is modeled after the behaviour of {@link * https://w3c.github.io/webdriver/#actions|WebDriver Actions Command} * diff --git a/tests/wpt/web-platform-tests/resources/testharness.js b/tests/wpt/web-platform-tests/resources/testharness.js index 61eadfeb654..2c7dcd8df4a 100644 --- a/tests/wpt/web-platform-tests/resources/testharness.js +++ b/tests/wpt/web-platform-tests/resources/testharness.js @@ -535,12 +535,13 @@ policies and contribution forms [3]. var value = test_obj.step(func, test_obj, test_obj); if (value !== undefined) { - var msg = "Test named \"" + test_name + - "\" inappropriately returned a value"; + var msg = 'Test named "' + test_name + + '" passed a function to `test` that returned a value.'; try { - if (value && value.hasOwnProperty("then")) { - msg += ", consider using `promise_test` instead"; + if (value && typeof value.then === 'function') { + msg += ' Consider using `promise_test` instead when ' + + 'using Promises or async/await.'; } } catch (err) {} @@ -568,7 +569,30 @@ policies and contribution forms [3]. var test_name = name ? name : test_environment.next_default_test_name(); var test_obj = new Test(test_name, properties); if (func) { - test_obj.step(func, test_obj, test_obj); + var value = test_obj.step(func, test_obj, test_obj); + + // Test authors sometimes return values to async_test, expecting us + // to handle the value somehow. Make doing so a harness error to be + // clear this is invalid, and point authors to promise_test if it + // may be appropriate. + // + // Note that we only perform this check on the initial function + // passed to async_test, not on any later steps - we haven't seen a + // consistent problem with those (and it's harder to check). + if (value !== undefined) { + var msg = 'Test named "' + test_name + + '" passed a function to `async_test` that returned a value.'; + + try { + if (value && typeof value.then === 'function') { + msg += ' Consider using `promise_test` instead when ' + + 'using Promises or async/await.'; + } + } catch (err) {} + + tests.set_status(tests.status.ERROR, msg); + tests.complete(); + } } return test_obj; } diff --git a/tests/wpt/web-platform-tests/serial/serial-allowed-by-feature-policy.https.sub.html b/tests/wpt/web-platform-tests/serial/serial-allowed-by-feature-policy.https.sub.html index 316256bbbb4..36990694d19 100644 --- a/tests/wpt/web-platform-tests/serial/serial-allowed-by-feature-policy.https.sub.html +++ b/tests/wpt/web-platform-tests/serial/serial-allowed-by-feature-policy.https.sub.html @@ -1,4 +1,5 @@ + @@ -27,15 +28,19 @@ async_test(t => { expect_feature_available_default); }, header + ' allows workers in same-origin iframes.'); +// Set allow="serial" on iframe element to delegate 'serial' to cross origin +// subframe. async_test(t => { test_feature_availability('serial.getPorts()', t, cross_origin_src, - expect_feature_available_default); + expect_feature_available_default, 'serial'); }, header + ' allows cross-origin iframes.'); +// Set allow="serial" on iframe element to delegate 'serial' to cross origin +// subframe. async_test(t => { test_feature_availability('serial.getPorts()', t, cross_origin_worker_frame_src, - expect_feature_available_default); + expect_feature_available_default, 'serial'); }, header + ' allows workers in cross-origin iframes.'); fetch_tests_from_worker(new Worker( diff --git a/tests/wpt/web-platform-tests/svg/painting/svg-child-will-change-transform-invalidation-ref.html b/tests/wpt/web-platform-tests/svg/painting/svg-child-will-change-transform-invalidation-ref.html new file mode 100644 index 00000000000..0d760d18dce --- /dev/null +++ b/tests/wpt/web-platform-tests/svg/painting/svg-child-will-change-transform-invalidation-ref.html @@ -0,0 +1,2 @@ + +
diff --git a/tests/wpt/web-platform-tests/svg/painting/svg-child-will-change-transform-invalidation.html b/tests/wpt/web-platform-tests/svg/painting/svg-child-will-change-transform-invalidation.html new file mode 100644 index 00000000000..962ba5abeb7 --- /dev/null +++ b/tests/wpt/web-platform-tests/svg/painting/svg-child-will-change-transform-invalidation.html @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/tests/wpt/web-platform-tests/tools/ci/tc/tasks/test.yml b/tests/wpt/web-platform-tests/tools/ci/tc/tasks/test.yml index c3fccff0655..1b5c7acfd74 100644 --- a/tests/wpt/web-platform-tests/tools/ci/tc/tasks/test.yml +++ b/tests/wpt/web-platform-tests/tools/ci/tc/tasks/test.yml @@ -317,6 +317,8 @@ tasks: --channel=${vars.channel} --verify --verify-no-chaos-mode + --verify-repeat-loop=0 + --verify-repeat-restart=10 --github-checks-text-file="/home/test/artifacts/checkrun.md" - wpt-${vars.browser}-${vars.channel}-results: diff --git a/tests/wpt/web-platform-tests/tools/lint/lint.py b/tests/wpt/web-platform-tests/tools/lint/lint.py index bb49d8970d9..5027da70577 100644 --- a/tests/wpt/web-platform-tests/tools/lint/lint.py +++ b/tests/wpt/web-platform-tests/tools/lint/lint.py @@ -6,6 +6,7 @@ import ast import io import json import logging +import multiprocessing import os import re import subprocess @@ -34,6 +35,7 @@ if MYPY: from typing import Callable from typing import Dict from typing import IO + from typing import Iterator from typing import Iterable from typing import List from typing import Optional @@ -42,6 +44,7 @@ if MYPY: from typing import Text from typing import Tuple from typing import Type + from typing import TypeVar # The Ignorelist is a two level dictionary. The top level is indexed by # error names (e.g. 'TRAILING WHITESPACE'). Each of those then has a map of @@ -50,12 +53,26 @@ if MYPY: # ignores the error. Ignorelist = Dict[Text, Dict[Text, Set[Optional[int]]]] + # Define an arbitrary typevar + T = TypeVar("T") + try: from xml.etree import cElementTree as ElementTree except ImportError: from xml.etree import ElementTree as ElementTree # type: ignore +if sys.version_info >= (3, 7): + from contextlib import nullcontext +else: + from contextlib import contextmanager + + @contextmanager + def nullcontext(enter_result=None): + # type: (Optional[T]) -> Iterator[Optional[T]] + yield enter_result + + logger = None # type: Optional[logging.Logger] @@ -793,8 +810,8 @@ def check_all_paths(repo_root, paths): return errors -def check_file_contents(repo_root, path, f): - # type: (Text, Text, IO[bytes]) -> List[rules.Error] +def check_file_contents(repo_root, path, f=None): + # type: (Text, Text, Optional[IO[bytes]]) -> List[rules.Error] """ Runs lints that check the file contents. @@ -803,12 +820,18 @@ def check_file_contents(repo_root, path, f): :param f: a file-like object with the file contents :returns: a list of errors found in ``f`` """ + with io.open(os.path.join(repo_root, path), 'rb') if f is None else nullcontext(f) as real_f: + assert real_f is not None # Py2: prod mypy -2 into accepting this isn't None + errors = [] + for file_fn in file_lints: + errors.extend(file_fn(repo_root, path, real_f)) + real_f.seek(0) + return errors - errors = [] - for file_fn in file_lints: - errors.extend(file_fn(repo_root, path, f)) - f.seek(0) - return errors + +def check_file_contents_apply(args): + # type: (Tuple[Text, Text]) -> List[rules.Error] + return check_file_contents(*args) def output_errors_text(log, errors): @@ -964,6 +987,10 @@ def main(**kwargs_str): return lint(repo_root, paths, output_format, ignore_glob, github_checks_outputter) +# best experimental guess at a decent cut-off for using the parallel path +MIN_FILES_FOR_PARALLEL = 80 + + def lint(repo_root, paths, output_format, ignore_glob=None, github_checks_outputter=None): # type: (Text, List[Text], Text, Optional[List[Text]], Optional[GitHubChecksOutputter]) -> int error_count = defaultdict(int) # type: Dict[Text, int] @@ -1005,26 +1032,47 @@ def lint(repo_root, paths, output_format, ignore_glob=None, github_checks_output return (errors[-1][0], path) - for path in paths[:]: + to_check_content = [] + skip = set() + + for path in paths: abs_path = os.path.join(repo_root, path) if not os.path.exists(abs_path): - paths.remove(path) + skip.add(path) continue if any(fnmatch.fnmatch(path, file_match) for file_match in skipped_files): - paths.remove(path) + skip.add(path) continue errors = check_path(repo_root, path) last = process_errors(errors) or last if not os.path.isdir(abs_path): - with io.open(abs_path, 'rb') as test_file: - errors = check_file_contents(repo_root, path, test_file) - last = process_errors(errors) or last + to_check_content.append((repo_root, path)) - errors = check_all_paths(repo_root, paths) - last = process_errors(errors) or last + paths = [p for p in paths if p not in skip] + + if len(to_check_content) >= MIN_FILES_FOR_PARALLEL: + pool = multiprocessing.Pool() + # submit this job first, as it's the longest running + all_paths_result = pool.apply_async(check_all_paths, (repo_root, paths)) + # each item tends to be quick, so pass things in large chunks to avoid too much IPC overhead + errors_it = pool.imap_unordered(check_file_contents_apply, to_check_content, chunksize=40) + pool.close() + for errors in errors_it: + last = process_errors(errors) or last + + errors = all_paths_result.get() + pool.join() + last = process_errors(errors) or last + else: + for item in to_check_content: + errors = check_file_contents(*item) + last = process_errors(errors) or last + + errors = check_all_paths(repo_root, paths) + last = process_errors(errors) or last if output_format in ("normal", "markdown"): output_error_count(error_count) diff --git a/tests/wpt/web-platform-tests/tools/webdriver/webdriver/client.py b/tests/wpt/web-platform-tests/tools/webdriver/webdriver/client.py index 1a4b6498312..adc9b3a3f70 100644 --- a/tests/wpt/web-platform-tests/tools/webdriver/webdriver/client.py +++ b/tests/wpt/web-platform-tests/tools/webdriver/webdriver/client.py @@ -244,10 +244,6 @@ class Actions(object): """ body = {"actions": [] if actions is None else actions} actions = self.session.send_session_command("POST", "actions", body) - """WebDriver window should be set to the top level window when wptrunner - processes the next event. - """ - self.session.switch_frame(None) return actions @command @@ -347,9 +343,7 @@ class Find(object): self.session = session @command - def css(self, element_selector, all=True, frame="window"): - if (frame != "window"): - self.session.switch_frame(frame) + def css(self, element_selector, all=True): elements = self._find_element("css selector", element_selector, all) return elements diff --git a/tests/wpt/web-platform-tests/tools/wpt/browser.py b/tests/wpt/web-platform-tests/tools/wpt/browser.py index 6f9da16c50b..35f60d1620b 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/browser.py +++ b/tests/wpt/web-platform-tests/tools/wpt/browser.py @@ -657,35 +657,31 @@ class Chrome(Browser): return None def find_webdriver(self, channel=None, browser_binary=None): - chromedriver_binary = find_executable("chromedriver") - if not chromedriver_binary: - return chromedriver_binary + return find_executable("chromedriver") - chromedriver_version = self.webdriver_version(chromedriver_binary) + def webdriver_supports_browser(self, webdriver_binary, browser_binary): + chromedriver_version = self.webdriver_version(webdriver_binary) if not chromedriver_version: self.logger.warning( "Unable to get version for ChromeDriver %s, rejecting it" % - chromedriver_binary) - return None + webdriver_binary) + return False - if not browser_binary: - browser_binary = self.find_binary(channel) browser_version = self.version(browser_binary) if not browser_version: # If we can't get the browser version, we just have to assume the # ChromeDriver is good. - return chromedriver_binary + return True # Check that the ChromeDriver version matches the Chrome version. chromedriver_major = chromedriver_version.split('.')[0] browser_major = browser_version.split('.')[0] if chromedriver_major != browser_major: self.logger.warning( - "Found ChromeDriver %s; does not match Chrome/Chromium %s" % + "ChromeDriver %s does not match Chrome/Chromium %s" % (chromedriver_version, browser_version)) - return None - - return chromedriver_binary + return False + return True def _official_chromedriver_url(self, chrome_version): # http://chromedriver.chromium.org/downloads/version-selection @@ -1070,6 +1066,29 @@ class EdgeChromium(Browser): def find_webdriver(self, channel=None): return find_executable("msedgedriver") + def webdriver_supports_browser(self, webdriver_binary, browser_binary): + edgedriver_version = self.webdriver_version(webdriver_binary) + if not edgedriver_version: + self.logger.warning( + "Unable to get version for EdgeDriver %s, rejecting it" % + webdriver_binary) + return False + + browser_version = self.version(browser_binary) + if not browser_version: + # If we can't get the browser version, we just have to assume the + # EdgeDriver is good. + return True + + # Check that the EdgeDriver version matches the Edge version. + edgedriver_major = edgedriver_version.split('.')[0] + browser_major = browser_version.split('.')[0] + if edgedriver_major != browser_major: + self.logger.warning("EdgeDriver %s does not match Edge %s" % + (edgedriver_version, browser_version)) + return False + return True + def install_webdriver(self, dest=None, channel=None, browser_binary=None): if self.platform != "win" and self.platform != "macos": raise ValueError("Only Windows and Mac platforms are currently supported") @@ -1128,6 +1147,21 @@ class EdgeChromium(Browser): self.logger.warning("Failed to find Edge binary.") return None + def webdriver_version(self, webdriver_binary): + if self.platform == "win": + return _get_fileversion(webdriver_binary, self.logger) + + try: + version_string = call(webdriver_binary, "--version").strip() + except subprocess.CalledProcessError: + self.logger.warning("Failed to call %s" % webdriver_binary) + return None + m = re.match(r"MSEdgeDriver ([0-9][0-9.]*)", version_string) + if not m: + self.logger.warning("Failed to extract version from: %s" % version_string) + return None + return m.group(1) + class Edge(Browser): """Edge-specific interface.""" diff --git a/tests/wpt/web-platform-tests/tools/wpt/run.py b/tests/wpt/web-platform-tests/tools/wpt/run.py index 8edaa60ab03..f289e097753 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/run.py +++ b/tests/wpt/web-platform-tests/tools/wpt/run.py @@ -338,10 +338,10 @@ class Chrome(BrowserSetup): if kwargs["webdriver_binary"] is None: webdriver_binary = None if not kwargs["install_webdriver"]: - webdriver_binary = self.browser.find_webdriver( - channel=kwargs["browser_channel"], - browser_binary=kwargs["binary"] - ) + webdriver_binary = self.browser.find_webdriver() + if webdriver_binary and not self.browser.webdriver_supports_browser( + webdriver_binary, kwargs["binary"]): + webdriver_binary = None if webdriver_binary is None: install = self.prompt_install("chromedriver") @@ -516,13 +516,16 @@ class EdgeChromium(BrowserSetup): kwargs["binary"] = binary else: raise WptrunError("Unable to locate Edge binary") + if kwargs["webdriver_binary"] is None: webdriver_binary = None if not kwargs["install_webdriver"]: webdriver_binary = self.browser.find_webdriver() + if (webdriver_binary and not self.browser.webdriver_supports_browser( + webdriver_binary, kwargs["binary"])): + webdriver_binary = None - # Install browser if none are found or if it's found in venv path - if webdriver_binary is None or webdriver_binary in self.venv.bin_path: + if webdriver_binary is None: install = self.prompt_install("msedgedriver") if install: diff --git a/tests/wpt/web-platform-tests/tools/wpt/tests/test_browser.py b/tests/wpt/web-platform-tests/tools/wpt/tests/test_browser.py index 350ee9b5eae..0abdb9d45b8 100644 --- a/tests/wpt/web-platform-tests/tools/wpt/tests/test_browser.py +++ b/tests/wpt/web-platform-tests/tools/wpt/tests/test_browser.py @@ -24,39 +24,81 @@ def test_all_browser_abc(): assert not inspect.isabstract(cls), "%s is abstract" % name -@mock.patch('tools.wpt.browser.find_executable') -def test_chrome_find_webdriver(mocked_find_executable): - # Cannot find ChromeDriver - chrome = browser.Chrome(logger) - mocked_find_executable.return_value = None - assert chrome.find_webdriver() is None +def test_edgechromium_webdriver_supports_browser(): + # EdgeDriver binary cannot be called. + edge = browser.EdgeChromium(logger) + edge.webdriver_version = mock.MagicMock(return_value=None) + assert not edge.webdriver_supports_browser('/usr/bin/edgedriver', '/usr/bin/edge') + # Browser binary cannot be called. + edge = browser.EdgeChromium(logger) + edge.webdriver_version = mock.MagicMock(return_value='70.0.1') + edge.version = mock.MagicMock(return_value=None) + assert edge.webdriver_supports_browser('/usr/bin/edgedriver', '/usr/bin/edge') + + # Browser version matches. + edge = browser.EdgeChromium(logger) + edge.webdriver_version = mock.MagicMock(return_value='70.0.1') + edge.version = mock.MagicMock(return_value='70.1.5') + assert edge.webdriver_supports_browser('/usr/bin/edgedriver', '/usr/bin/edge') + + # Browser version doesn't match. + edge = browser.EdgeChromium(logger) + edge.webdriver_version = mock.MagicMock(return_value='70.0.1') + edge.version = mock.MagicMock(return_value='69.0.1') + assert not edge.webdriver_supports_browser('/usr/bin/edgedriver', '/usr/bin/edge') + + +# On Windows, webdriver_version directly calls _get_fileversion, so there is no +# logic to test there. +@pytest.mark.skipif(sys.platform.startswith('win'), reason='just uses _get_fileversion on Windows') +@mock.patch('tools.wpt.browser.call') +def test_edgechromium_webdriver_version(mocked_call): + edge = browser.EdgeChromium(logger) + webdriver_binary = '/usr/bin/edgedriver' + + # Working cases. + mocked_call.return_value = 'MSEdgeDriver 84.0.4147.30' + assert edge.webdriver_version(webdriver_binary) == '84.0.4147.30' + mocked_call.return_value = 'MSEdgeDriver 87.0.1 (abcd1234-refs/branch-heads/4147@{#310})' + assert edge.webdriver_version(webdriver_binary) == '87.0.1' + + # Various invalid version strings + mocked_call.return_value = 'Edge 84.0.4147.30 (dev)' + assert edge.webdriver_version(webdriver_binary) is None + mocked_call.return_value = 'MSEdgeDriver New 84.0.4147.30' + assert edge.webdriver_version(webdriver_binary) is None + mocked_call.return_value = '' + assert edge.webdriver_version(webdriver_binary) is None + + # The underlying subprocess call throws. + mocked_call.side_effect = subprocess.CalledProcessError(5, 'cmd', output='Call failed') + assert edge.webdriver_version(webdriver_binary) is None + + +def test_chrome_webdriver_supports_browser(): # ChromeDriver binary cannot be called. chrome = browser.Chrome(logger) - mocked_find_executable.return_value = '/usr/bin/chromedriver' chrome.webdriver_version = mock.MagicMock(return_value=None) - assert chrome.find_webdriver() is None + assert not chrome.webdriver_supports_browser('/usr/bin/chromedriver', '/usr/bin/chrome') # Browser binary cannot be called. chrome = browser.Chrome(logger) - mocked_find_executable.return_value = '/usr/bin/chromedriver' chrome.webdriver_version = mock.MagicMock(return_value='70.0.1') chrome.version = mock.MagicMock(return_value=None) - assert chrome.find_webdriver(browser_binary='/usr/bin/chrome') == '/usr/bin/chromedriver' + assert chrome.webdriver_supports_browser('/usr/bin/chromedriver', '/usr/bin/chrome') # Browser version matches. chrome = browser.Chrome(logger) - mocked_find_executable.return_value = '/usr/bin/chromedriver' chrome.webdriver_version = mock.MagicMock(return_value='70.0.1') chrome.version = mock.MagicMock(return_value='70.1.5') - assert chrome.find_webdriver(browser_binary='/usr/bin/chrome') == '/usr/bin/chromedriver' + assert chrome.webdriver_supports_browser('/usr/bin/chromedriver', '/usr/bin/chrome') # Browser version doesn't match. chrome = browser.Chrome(logger) - mocked_find_executable.return_value = '/usr/bin/chromedriver' chrome.webdriver_version = mock.MagicMock(return_value='70.0.1') chrome.version = mock.MagicMock(return_value='69.0.1') - assert chrome.find_webdriver(browser_binary='/usr/bin/chrome') is None + assert not chrome.webdriver_supports_browser('/usr/bin/chromedriver', '/usr/bin/chrome') # On Windows, webdriver_version directly calls _get_fileversion, so there is no diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/edgechromium.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/edgechromium.py index 5c51f066f04..0bcb18cdc0c 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/edgechromium.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/browsers/edgechromium.py @@ -39,7 +39,7 @@ def executor_kwargs(test_type, server_config, cache_manager, run_info_data, executor_kwargs["supports_eager_pageload"] = False capabilities = { - "goog:chromeOptions": { + "ms:edgeOptions": { "prefs": { "profile": { "default_content_setting_values": { @@ -58,13 +58,13 @@ def executor_kwargs(test_type, server_config, cache_manager, run_info_data, for (kwarg, capability) in [("binary", "binary"), ("binary_args", "args")]: if kwargs[kwarg] is not None: - capabilities["goog:chromeOptions"][capability] = kwargs[kwarg] + capabilities["ms:edgeOptions"][capability] = kwargs[kwarg] if kwargs["headless"]: - if "args" not in capabilities["goog:chromeOptions"]: - capabilities["goog:chromeOptions"]["args"] = [] - if "--headless" not in capabilities["goog:chromeOptions"]["args"]: - capabilities["goog:chromeOptions"]["args"].append("--headless") + if "args" not in capabilities["ms:edgeOptions"]: + capabilities["ms:edgeOptions"]["args"] = [] + if "--headless" not in capabilities["ms:edgeOptions"]["args"]: + capabilities["ms:edgeOptions"]["args"].append("--headless") executor_kwargs["capabilities"] = capabilities diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/actions.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/actions.py index fc43dd665a2..b11d30bb94a 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/actions.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/actions.py @@ -43,12 +43,12 @@ class ActionSequenceAction(object): for action in actionSequence["actions"]: if (action["type"] == "pointerMove" and isinstance(action["origin"], dict)): - action["origin"] = self.get_element(action["origin"]["selector"], action["frame"]["frame"]) + action["origin"] = self.get_element(action["origin"]["selector"]) self.protocol.action_sequence.send_actions({"actions": actions}) - def get_element(self, element_selector, frame): - element = self.protocol.select.element_by_selector(element_selector, frame) - return element + def get_element(self, element_selector): + return self.protocol.select.element_by_selector(element_selector) + class GenerateTestReportAction(object): name = "generate_test_report" diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/base.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/base.py index b5820a38085..0247b3feed2 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/base.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/base.py @@ -793,7 +793,8 @@ class CallbackHandler(object): except KeyError: raise ValueError("Unknown action %s" % action) try: - result = action_handler(payload) + with ActionContext(self.logger, self.protocol, payload.get("context")): + result = action_handler(payload) except self.unimplemented_exc: self.logger.warning("Action %s not implemented" % action) self._send_message("complete", "error", "Action %s not implemented" % action) @@ -811,3 +812,36 @@ class CallbackHandler(object): def _send_message(self, message_type, status, message=None): self.protocol.testdriver.send_message(message_type, status, message=message) + + +class ActionContext(object): + def __init__(self, logger, protocol, context): + self.logger = logger + self.protocol = protocol + self.context = context + self.initial_window = None + self.switched_frame = False + + def __enter__(self): + if self.context is None: + return + + window_id = self.context[0] + if window_id: + self.initial_window = self.protocol.base.current_window + self.logger.debug("Switching to window %s" % window_id) + self.protocol.testdriver.switch_to_window(window_id) + + for frame_id in self.context[1:]: + self.switched_frame = True + self.logger.debug("Switching to frame %s" % frame_id) + self.protocol.testdriver.switch_to_frame(frame_id) + + def __exit__(self, *args): + if self.initial_window is not None: + self.logger.debug("Switching back to initial window") + self.protocol.base.set_window(self.initial_window) + self.initial_window = None + elif self.switched_frame: + self.protocol.testdriver.switch_to_frame(None) + self.switched_frame = False diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executormarionette.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executormarionette.py index 9c1f4f366d5..e7c9726974b 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executormarionette.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executormarionette.py @@ -429,9 +429,6 @@ class MarionetteSelectorProtocolPart(SelectorProtocolPart): def elements_by_selector(self, selector): return self.marionette.find_elements("css selector", selector) - def elements_by_selector_and_frame(self, element_selector, frame): - return self.marionette.find_elements("css selector", element_selector) - class MarionetteClickProtocolPart(ClickProtocolPart): def setup(self): @@ -472,6 +469,24 @@ class MarionetteTestDriverProtocolPart(TestDriverProtocolPart): obj["message"] = str(message) self.parent.base.execute_script("window.postMessage(%s, '*')" % json.dumps(obj)) + def switch_to_window(self, window_id): + if window_id is None: + return + + for window_handle in self.marionette.window_handles: + _switch_to_window(self.marionette, window_handle) + try: + handle_window_id = self.marionette.execute_script("return window.name") + except errors.JavascriptException: + continue + if str(handle_window_id) == window_id: + return + + raise Exception("Window with id %s not found" % window_id) + + def switch_to_frame(self, frame_number): + self.marionette.switch_to_frame(frame_number) + class MarionetteCoverageProtocolPart(CoverageProtocolPart): def setup(self): diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py index ad79600aeb8..139c475ce44 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/executorwebdriver.py @@ -177,9 +177,6 @@ class WebDriverSelectorProtocolPart(SelectorProtocolPart): def elements_by_selector(self, selector): return self.webdriver.find.css(selector) - def elements_by_selector_and_frame(self, element_selector, frame): - return self.webdriver.find.css(element_selector, frame=frame) - class WebDriverClickProtocolPart(ClickProtocolPart): def setup(self): @@ -226,6 +223,24 @@ class WebDriverTestDriverProtocolPart(TestDriverProtocolPart): obj["message"] = str(message) self.webdriver.execute_script("window.postMessage(%s, '*')" % json.dumps(obj)) + def switch_to_window(self, window_id): + if window_id is None: + return + + for window_handle in self.webdriver.handles: + self.webdriver.window_handle = window_handle + try: + handle_window_id = self.webdriver.execute_script("return window.name") + except client.JavascriptErrorException: + continue + if str(handle_window_id) == window_id: + return + + raise Exception("Window with id %s not found" % window_id) + + def switch_to_frame(self, frame_number): + self.webdriver.switch_frame(frame_number) + class WebDriverGenerateTestReportProtocolPart(GenerateTestReportProtocolPart): def setup(self): diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/protocol.py b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/protocol.py index 3f0e44339f9..1c85c0f8b96 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/protocol.py +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/executors/protocol.py @@ -245,15 +245,12 @@ class SelectorProtocolPart(ProtocolPart): name = "select" - def element_by_selector(self, element_selector, frame="window"): - elements = self.elements_by_selector_and_frame(element_selector, frame) - frame_name = "window" - if (frame != "window"): - frame_name = frame.id + def element_by_selector(self, element_selector): + elements = self.elements_by_selector(element_selector) if len(elements) == 0: - raise ValueError("Selector '%s' in frame '%s' matches no elements" % (element_selector, frame_name)) + raise ValueError("Selector '%s' matches no elements" % (element_selector,)) elif len(elements) > 1: - raise ValueError("Selector '%s' in frame '%s' matches multiple elements" % (element_selector, frame_name)) + raise ValueError("Selector '%s' matches multiple elements" % (element_selector,)) return elements[0] @abstractmethod @@ -264,13 +261,6 @@ class SelectorProtocolPart(ProtocolPart): :returns: A list of protocol-specific handles to elements""" pass - @abstractmethod - def elements_by_selector_and_frame(self, element_selector, frame): - """Select elements matching a CSS selector - :param str selector: The CSS selector - :returns: A list of protocol-specific handles to elements""" - pass - class ClickProtocolPart(ProtocolPart): """Protocol part for performing trusted clicks""" @@ -362,6 +352,18 @@ class TestDriverProtocolPart(ProtocolPart): :param str message: Additional data to add to the message.""" pass + def switch_to_window(self, wptrunner_id): + """Switch to a window given a wptrunner window id + + :param str wptrunner_id: window id""" + pass + + def switch_to_frame(self, index): + """Switch to a frame in the current window + + :param int index: Frame id""" + pass + class AssertsProtocolPart(ProtocolPart): """ProtocolPart that implements the functionality required to get a count of non-fatal diff --git a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/testdriver-extra.js b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/testdriver-extra.js index 5001b004f2e..241fc833951 100644 --- a/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/testdriver-extra.js +++ b/tests/wpt/web-platform-tests/tools/wptrunner/wptrunner/testdriver-extra.js @@ -16,27 +16,51 @@ } if (data.status === "success") { - result = JSON.parse(data.message).result + result = JSON.parse(data.message).result; pending_resolve(result); } else { pending_reject(`${data.status}: ${data.message}`); } }); - const get_frame = function(element, frame) { - let foundFrame = frame; - let frameDocument = frame == window ? window.document : frame.contentDocument; - if (!frameDocument.contains(element)) { - foundFrame = null; - let frames = document.getElementsByTagName("iframe"); - for (let i = 0; i < frames.length; i++) { - if (get_frame(element, frames[i])) { - foundFrame = frames[i]; - break; - } - } + let last_window_id = 0; + function get_window_id(win) { + if (win === window) { + return null; } - return foundFrame; + // This is a hack until some implementations support proper window ids + // It won't work cross-frame + if (!win.name) { + win.name = "__wptrunner_window " + last_window_id++; + } + return win.name; + } + + const get_context = function(element) { + if (!element) { + return null; + } + let elementWindow = element.ownerDocument.defaultView; + if (!elementWindow) { + throw new Error("Browsing context for element was detached"); + } + let top = elementWindow.top; + if (elementWindow === window) { + if (top !== window) { + throw new Error("Can't load testdriver in a frame"); + } + // For the current window just return null + return null; + } + let rv = []; + let currentWindow = elementWindow; + while (currentWindow !== top) { + rv.push(Array.prototype.indexOf.call(currentWindow.parent.frames, currentWindow)); + currentWindow = currentWindow.parent; + } + rv.push(top !== window ? get_window_id(top) : null); + rv.reverse(); + return rv; }; const get_selector = function(element) { @@ -72,22 +96,31 @@ window.test_driver_internal.in_automation = true; window.test_driver_internal.click = function(element) { + const context = get_context(element); const selector = get_selector(element); const pending_promise = new Promise(function(resolve, reject) { pending_resolve = resolve; pending_reject = reject; }); - window.__wptrunner_message_queue.push({"type": "action", "action": "click", "selector": selector}); + window.__wptrunner_message_queue.push({"type": "action", + "action": "click", + selector, + context}); return pending_promise; }; window.test_driver_internal.send_keys = function(element, keys) { const selector = get_selector(element); + const context = get_context(element); const pending_promise = new Promise(function(resolve, reject) { pending_resolve = resolve; pending_reject = reject; }); - window.__wptrunner_message_queue.push({"type": "action", "action": "send_keys", "selector": selector, "keys": keys}); + window.__wptrunner_message_queue.push({"type": "action", + "action": "send_keys", + selector, + keys, + context}); return pending_promise; }; @@ -96,24 +129,26 @@ pending_resolve = resolve; pending_reject = reject; }); + let context = null; for (let actionSequence of actions) { if (actionSequence.type == "pointer") { for (let action of actionSequence.actions) { // The origin of each action can only be an element or a string of a value "viewport" or "pointer". if (action.type == "pointerMove" && typeof(action.origin) != 'string') { - let frame = get_frame(action.origin, window); - if (frame != null) { - if (frame == window) - action.frame = {frame: "window"}; - else - action.frame = {frame: frame}; - action.origin = {selector: get_selector(action.origin)}; + let action_context = get_context(action.origin); + action.origin = {selector: get_selector(action.origin)}; + if (context !== null && action_context !== context) { + throw new Error("Actions must be in a single context"); } + context = action_context; } } } } - window.__wptrunner_message_queue.push({"type": "action", "action": "action_sequence", "actions": actions}); + window.__wptrunner_message_queue.push({type: "action", + action: "action_sequence", + actions, + context}); return pending_promise; }; @@ -122,7 +157,9 @@ pending_resolve = resolve; pending_reject = reject; }); - window.__wptrunner_message_queue.push({"type": "action", "action": "generate_test_report", "message": message}); + window.__wptrunner_message_queue.push({"type": "action", + "action": "generate_test_report", + message}); return pending_promise; }; @@ -131,7 +168,9 @@ pending_resolve = resolve; pending_reject = reject; }); - window.__wptrunner_message_queue.push({"type": "action", "action": "set_permission", permission_params}); + window.__wptrunner_message_queue.push({"type": "action", + "action": "set_permission", + permission_params}); return pending_promise; }; @@ -140,7 +179,9 @@ pending_resolve = resolve; pending_reject = reject; }); - window.__wptrunner_message_queue.push({"type": "action", "action": "add_virtual_authenticator", config}); + window.__wptrunner_message_queue.push({"type": "action", + "action": "add_virtual_authenticator", + config}); return pending_promise; }; @@ -149,7 +190,9 @@ pending_resolve = resolve; pending_reject = reject; }); - window.__wptrunner_message_queue.push({"type": "action", "action": "remove_virtual_authenticator", authenticator_id}); + window.__wptrunner_message_queue.push({"type": "action", + "action": "remove_virtual_authenticator", + authenticator_id}); return pending_promise; }; @@ -158,7 +201,9 @@ pending_resolve = resolve; pending_reject = reject; }); - window.__wptrunner_message_queue.push({"type": "action", "action": "add_credential", authenticator_id, credential}); + window.__wptrunner_message_queue.push({"type": "action", + "action": "add_credential", + authenticator_id, credential}); return pending_promise; }; @@ -167,7 +212,9 @@ pending_resolve = resolve; pending_reject = reject; }); - window.__wptrunner_message_queue.push({"type": "action", "action": "get_credentials", authenticator_id}); + window.__wptrunner_message_queue.push({"type": "action", + "action": "get_credentials", + authenticator_id}); return pending_promise; }; @@ -176,7 +223,10 @@ pending_resolve = resolve; pending_reject = reject; }); - window.__wptrunner_message_queue.push({"type": "action", "action": "remove_credential", authenticator_id, credential_id}); + window.__wptrunner_message_queue.push({"type": "action", + "action": "remove_credential", + authenticator_id, + credential_id}); return pending_promise; }; @@ -185,7 +235,9 @@ pending_resolve = resolve; pending_reject = reject; }); - window.__wptrunner_message_queue.push({"type": "action", "action": "remove_all_credentials", authenticator_id}); + window.__wptrunner_message_queue.push({"type": "action", + "action": "remove_all_credentials", + authenticator_id}); return pending_promise; }; @@ -194,7 +246,10 @@ pending_resolve = resolve; pending_reject = reject; }); - window.__wptrunner_message_queue.push({"type": "action", "action": "set_user_verified", authenticator_id, uv}); + window.__wptrunner_message_queue.push({"type": "action", + "action": "set_user_verified", + authenticator_id, + uv}); return pending_promise; }; })();