From 592c839b4cc9b1661f0b00c4a1a467d519255e7c Mon Sep 17 00:00:00 2001 From: Maksim Sadym <69349599+sadym-chromium@users.noreply.github.com> Date: Fri, 31 May 2024 10:35:08 +0200 Subject: [PATCH] feat: send `browsingContext.contextCreated` event while subscribing (#2255) Subscribing to `browsingContext.contextCreated` should emit events for the exiting contexts. This required to add subscribe hooks to event manager. Hooks are engaged when the subscription is done for the required method. Spec: https://w3c.github.io/webdriver-bidi/#ref-for-event-remote-end-subscribe-steps%E2%91%A1 Addressing WPT test [**webdriver/tests/bidi/browsing_context/context_created/context_created.py:test_existing_context**](https://wpt.fyi/results/webdriver/tests/bidi/browsing_context/context_created/context_created.py?q=label%3Achromium-bidi-2023&run_id=6271852771278848&run_id=5105860015816704). --------- Signed-off-by: Browser Automation Bot Co-authored-by: Browser Automation Bot --- src/bidiMapper/CommandProcessor.ts | 3 +- .../context/BrowsingContextProcessor.ts | 32 +++- .../modules/session/EventManager.ts | 42 ++++- .../session/SubscriptionManager.spec.ts | 39 +++++ .../modules/session/SubscriptionManager.ts | 71 ++++++--- src/utils/DistinctValues.spec.ts | 149 ++++++++++++++++++ src/utils/DistinctValues.ts | 60 +++++++ tests/browsing_context/test_create.py | 57 ++++++- tests/session/test_subscription.py | 3 +- .../context_created/context_created.py.ini | 6 - .../context_created/context_created.py.ini | 6 - .../context_created/context_created.py.ini | 6 - 12 files changed, 427 insertions(+), 47 deletions(-) create mode 100644 src/utils/DistinctValues.spec.ts create mode 100644 src/utils/DistinctValues.ts delete mode 100644 wpt-metadata/chromedriver/headful/webdriver/tests/bidi/browsing_context/context_created/context_created.py.ini delete mode 100644 wpt-metadata/chromedriver/headless/webdriver/tests/bidi/browsing_context/context_created/context_created.py.ini delete mode 100644 wpt-metadata/mapper/headless/webdriver/tests/bidi/browsing_context/context_created/context_created.py.ini diff --git a/src/bidiMapper/CommandProcessor.ts b/src/bidiMapper/CommandProcessor.ts index bc31345af7..b5a0d7efa2 100644 --- a/src/bidiMapper/CommandProcessor.ts +++ b/src/bidiMapper/CommandProcessor.ts @@ -92,7 +92,8 @@ export class CommandProcessor extends EventEmitter { this.#browserProcessor = new BrowserProcessor(browserCdpClient); this.#browsingContextProcessor = new BrowsingContextProcessor( browserCdpClient, - browsingContextStorage + browsingContextStorage, + eventManager ); this.#cdpProcessor = new CdpProcessor( browsingContextStorage, diff --git a/src/bidiMapper/modules/context/BrowsingContextProcessor.ts b/src/bidiMapper/modules/context/BrowsingContextProcessor.ts index f64aa3df7d..9f407b98a3 100644 --- a/src/bidiMapper/modules/context/BrowsingContextProcessor.ts +++ b/src/bidiMapper/modules/context/BrowsingContextProcessor.ts @@ -19,12 +19,14 @@ import type {Protocol} from 'devtools-protocol'; import type {CdpClient} from '../../../cdp/CdpClient.js'; import { BrowsingContext, + ChromiumBidi, InvalidArgumentException, type EmptyResult, NoSuchUserContextException, NoSuchAlertException, } from '../../../protocol/protocol.js'; import {CdpErrorConstants} from '../../../utils/CdpErrorConstants.js'; +import type {EventManager} from '../session/EventManager.js'; import type {BrowsingContextImpl} from './BrowsingContextImpl.js'; import type {BrowsingContextStorage} from './BrowsingContextStorage.js'; @@ -32,13 +34,20 @@ import type {BrowsingContextStorage} from './BrowsingContextStorage.js'; export class BrowsingContextProcessor { readonly #browserCdpClient: CdpClient; readonly #browsingContextStorage: BrowsingContextStorage; + readonly #eventManager: EventManager; constructor( browserCdpClient: CdpClient, - browsingContextStorage: BrowsingContextStorage + browsingContextStorage: BrowsingContextStorage, + eventManager: EventManager ) { this.#browserCdpClient = browserCdpClient; this.#browsingContextStorage = browsingContextStorage; + this.#eventManager = eventManager; + this.#eventManager.addSubscribeHook( + ChromiumBidi.BrowsingContext.EventNames.ContextCreated, + this.#onContextCreatedSubscribeHook.bind(this) + ); } getTree( @@ -285,4 +294,25 @@ export class BrowsingContextProcessor { const context = this.#browsingContextStorage.getContext(params.context); return await context.locateNodes(params); } + + #onContextCreatedSubscribeHook( + contextId: BrowsingContext.BrowsingContext + ): Promise { + const context = this.#browsingContextStorage.getContext(contextId); + const contextsToReport = [ + context, + ...this.#browsingContextStorage.getContext(contextId).allChildren, + ]; + contextsToReport.forEach((context) => { + this.#eventManager.registerEvent( + { + type: 'event', + method: ChromiumBidi.BrowsingContext.EventNames.ContextCreated, + params: context.serializeToBidiValue(), + }, + context.id + ); + }); + return Promise.resolve(); + } } diff --git a/src/bidiMapper/modules/session/EventManager.ts b/src/bidiMapper/modules/session/EventManager.ts index 8fcb2be838..a1e63cc4b3 100644 --- a/src/bidiMapper/modules/session/EventManager.ts +++ b/src/bidiMapper/modules/session/EventManager.ts @@ -22,6 +22,7 @@ import { } from '../../../protocol/protocol.js'; import {Buffer} from '../../../utils/Buffer.js'; import {DefaultMap} from '../../../utils/DefaultMap.js'; +import {distinctValues} from '../../../utils/DistinctValues.js'; import {EventEmitter} from '../../../utils/EventEmitter.js'; import {IdWrapper} from '../../../utils/IdWrapper.js'; import type {Result} from '../../../utils/result.js'; @@ -74,6 +75,14 @@ const eventBufferLength: ReadonlyMap = new Map( [[ChromiumBidi.Log.EventNames.LogEntryAdded, 100]] ); +/** + * Subscription item is a pair of event name and context id. + */ +export type SubscriptionItem = { + contextId: BrowsingContext.BrowsingContext; + event: ChromiumBidi.EventNames; +}; + export class EventManager extends EventEmitter { /** * Maps event name to a set of contexts where this event already happened. @@ -97,11 +106,19 @@ export class EventManager extends EventEmitter { #lastMessageSent = new Map(); #subscriptionManager: SubscriptionManager; #browsingContextStorage: BrowsingContextStorage; + /** + * Map of event name to hooks to be called when client is subscribed to the event. + */ + #subscribeHooks: DefaultMap< + ChromiumBidi.EventNames, + ((contextId: BrowsingContext.BrowsingContext) => void)[] + >; constructor(browsingContextStorage: BrowsingContextStorage) { super(); this.#browsingContextStorage = browsingContextStorage; this.#subscriptionManager = new SubscriptionManager(browsingContextStorage); + this.#subscribeHooks = new DefaultMap(() => []); } get subscriptionManager(): SubscriptionManager { @@ -119,6 +136,13 @@ export class EventManager extends EventEmitter { return JSON.stringify({eventName, browsingContext, channel}); } + addSubscribeHook( + event: ChromiumBidi.EventNames, + hook: (contextId: BrowsingContext.BrowsingContext) => Promise + ): void { + this.#subscribeHooks.get(event).push(hook); + } + registerEvent( event: ChromiumBidi.Event, contextId: BrowsingContext.BrowsingContext | null @@ -172,9 +196,17 @@ export class EventManager extends EventEmitter { } } + // List of the subscription items that were actually added. Each contains a specific + // event and context. No domain event (like "network") or global context subscription + // (like null) are included. + const addedSubscriptionItems: SubscriptionItem[] = []; + for (const eventName of eventNames) { for (const contextId of contextIds) { - this.#subscriptionManager.subscribe(eventName, contextId, channel); + addedSubscriptionItems.push( + ...this.#subscriptionManager.subscribe(eventName, contextId, channel) + ); + for (const eventWrapper of this.#getBufferedEvents( eventName, contextId, @@ -193,6 +225,14 @@ export class EventManager extends EventEmitter { } } + // Iterate over all new subscription items and call hooks if any. There can be + // duplicates, e.g. when subscribing to the whole domain and some specific event in + // the same time ("network", "network.responseCompleted"). `distinctValues` guarantees + // that hooks are called only once per pair event + context. + distinctValues(addedSubscriptionItems).forEach(({contextId, event}) => { + this.#subscribeHooks.get(event).forEach((hook) => hook(contextId)); + }); + await this.toggleModulesIfNeeded(); } diff --git a/src/bidiMapper/modules/session/SubscriptionManager.spec.ts b/src/bidiMapper/modules/session/SubscriptionManager.spec.ts index 1d96edaa60..7f6f8d85a5 100644 --- a/src/bidiMapper/modules/session/SubscriptionManager.spec.ts +++ b/src/bidiMapper/modules/session/SubscriptionManager.spec.ts @@ -68,9 +68,48 @@ describe('SubscriptionManager', () => { return null; }); + browsingContextStorage.getTopLevelContexts = sinon.stub().callsFake(() => { + return [{id: SOME_CONTEXT}, {id: ANOTHER_CONTEXT}]; + }); + subscriptionManager = new SubscriptionManager(browsingContextStorage); }); + describe('subscribe should return list of added subscriptions', () => { + describe('specific context', () => { + it('new subscription', () => { + expect( + subscriptionManager.subscribe(SOME_EVENT, SOME_CONTEXT, SOME_CHANNEL) + ).to.deep.equal([{event: SOME_EVENT, contextId: SOME_CONTEXT}]); + }); + + it('existing subscription', () => { + subscriptionManager.subscribe(SOME_EVENT, SOME_CONTEXT, SOME_CHANNEL); + expect( + subscriptionManager.subscribe(SOME_EVENT, SOME_CONTEXT, SOME_CHANNEL) + ).to.deep.equal([]); + }); + }); + + describe('global', () => { + it('new subscription', () => { + expect( + subscriptionManager.subscribe(SOME_EVENT, null, SOME_CHANNEL) + ).to.deep.equal([ + {event: SOME_EVENT, contextId: SOME_CONTEXT}, + {event: SOME_EVENT, contextId: ANOTHER_CONTEXT}, + ]); + }); + + it('existing subscription', () => { + subscriptionManager.subscribe(SOME_EVENT, SOME_CONTEXT, SOME_CHANNEL); + expect( + subscriptionManager.subscribe(SOME_EVENT, null, SOME_CHANNEL) + ).to.deep.equal([{event: SOME_EVENT, contextId: ANOTHER_CONTEXT}]); + }); + }); + }); + it('should subscribe twice to global and specific event in proper order', () => { subscriptionManager.subscribe(SOME_EVENT, null, SOME_CHANNEL); subscriptionManager.subscribe(SOME_EVENT, null, ANOTHER_CHANNEL); diff --git a/src/bidiMapper/modules/session/SubscriptionManager.ts b/src/bidiMapper/modules/session/SubscriptionManager.ts index 1960cc8790..fd055ca5b2 100644 --- a/src/bidiMapper/modules/session/SubscriptionManager.ts +++ b/src/bidiMapper/modules/session/SubscriptionManager.ts @@ -17,12 +17,13 @@ import type {BidiPlusChannel} from '../../../protocol/chromium-bidi.js'; import { + type BrowsingContext, ChromiumBidi, InvalidArgumentException, - type BrowsingContext, } from '../../../protocol/protocol.js'; import type {BrowsingContextStorage} from '../context/BrowsingContextStorage.js'; +import type {SubscriptionItem} from './EventManager.js'; import {isCdpEvent} from './events.js'; /** @@ -204,36 +205,50 @@ export class SubscriptionManager { return false; } + /** + * Subscribes to event in the given context and channel. + * @param {EventNames} event + * @param {BrowsingContext.BrowsingContext | null} contextId + * @param {BidiPlusChannel} channel + * @return {SubscriptionItem[]} List of + * subscriptions. If the event is a whole module, it will return all the specific + * events. If the contextId is null, it will return all the top-level contexts which were + * not subscribed before the command. + */ subscribe( event: ChromiumBidi.EventNames, contextId: BrowsingContext.BrowsingContext | null, channel: BidiPlusChannel - ): void { + ): SubscriptionItem[] { // All the subscriptions are handled on the top-level contexts. contextId = this.#browsingContextStorage.findTopLevelContextId(contextId); // Check if subscribed event is a whole module switch (event) { case ChromiumBidi.BiDiModule.BrowsingContext: - Object.values(ChromiumBidi.BrowsingContext.EventNames).map( - (specificEvent) => this.subscribe(specificEvent, contextId, channel) - ); - return; + return Object.values(ChromiumBidi.BrowsingContext.EventNames) + .map((specificEvent) => + this.subscribe(specificEvent, contextId, channel) + ) + .flat(); case ChromiumBidi.BiDiModule.Log: - Object.values(ChromiumBidi.Log.EventNames).map((specificEvent) => - this.subscribe(specificEvent, contextId, channel) - ); - return; + return Object.values(ChromiumBidi.Log.EventNames) + .map((specificEvent) => + this.subscribe(specificEvent, contextId, channel) + ) + .flat(); case ChromiumBidi.BiDiModule.Network: - Object.values(ChromiumBidi.Network.EventNames).map((specificEvent) => - this.subscribe(specificEvent, contextId, channel) - ); - return; + return Object.values(ChromiumBidi.Network.EventNames) + .map((specificEvent) => + this.subscribe(specificEvent, contextId, channel) + ) + .flat(); case ChromiumBidi.BiDiModule.Script: - Object.values(ChromiumBidi.Script.EventNames).map((specificEvent) => - this.subscribe(specificEvent, contextId, channel) - ); - return; + return Object.values(ChromiumBidi.Script.EventNames) + .map((specificEvent) => + this.subscribe(specificEvent, contextId, channel) + ) + .flat(); default: // Intentionally left empty. } @@ -248,12 +263,24 @@ export class SubscriptionManager { } const eventMap = contextToEventMap.get(contextId)!; - // Do not re-subscribe to events to keep the priority. - if (eventMap.has(event)) { - return; + const affectedContextIds = ( + contextId === null + ? this.#browsingContextStorage.getTopLevelContexts().map((c) => c.id) + : [contextId] + ) + // There can be contexts that are already subscribed to the event. Do not include + // them to the output. + .filter((contextId) => !this.isSubscribedTo(event, contextId)); + + if (!eventMap.has(event)) { + // Add subscription only if it's not already subscribed. + eventMap.set(event, this.#subscriptionPriority++); } - eventMap.set(event, this.#subscriptionPriority++); + return affectedContextIds.map((contextId) => ({ + event, + contextId, + })); } /** diff --git a/src/utils/DistinctValues.spec.ts b/src/utils/DistinctValues.spec.ts new file mode 100644 index 0000000000..bb5004b7ab --- /dev/null +++ b/src/utils/DistinctValues.spec.ts @@ -0,0 +1,149 @@ +/* + * Copyright 2024 Google LLC. + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {expect} from 'chai'; + +import {deterministicJSONStringify, distinctValues} from './DistinctValues.js'; + +describe('functions from DistinctValues.ts', () => { + describe('deterministicJSONStringify', () => { + it('should sort properties alphabetically', () => { + const data = {c: 3, b: 2, a: 1}; + const expected = '{"a":1,"b":2,"c":3}'; + const result = deterministicJSONStringify(data); + expect(result).to.equal(expected); + }); + + it('should handle arrays (preserving order)', () => { + const data = [3, 1, 2]; + const expected = '[3,1,2]'; + const result = deterministicJSONStringify(data); + expect(result).to.equal(expected); + }); + + describe('should handling data type', () => { + it('string', () => { + const data = 'Hello'; + const expected = '"Hello"'; + const result = deterministicJSONStringify(data); + expect(result).to.equal(expected); + }); + + it('number', () => { + const data = 42; + const expected = '42'; + const result = deterministicJSONStringify(data); + expect(result).to.equal(expected); + }); + + it('boolean', () => { + const data = true; + const expected = 'true'; + const result = deterministicJSONStringify(data); + expect(result).to.equal(expected); + }); + + it('null', () => { + const data = null; + const expected = 'null'; + const result = deterministicJSONStringify(data); + expect(result).to.equal(expected); + }); + + it('undefined', () => { + const data = undefined; + const expected = undefined; + const result = deterministicJSONStringify(data); + expect(result).to.equal(expected); + }); + }); + + describe('should handling different nested data types', () => { + it('string', () => { + const data = {nested: 'Hello'}; + const expected = '{"nested":"Hello"}'; + const result = deterministicJSONStringify(data); + expect(result).to.equal(expected); + }); + + it('number', () => { + const data = {nested: 42}; + const expected = '{"nested":42}'; + const result = deterministicJSONStringify(data); + expect(result).to.equal(expected); + }); + + it('boolean', () => { + const data = {nested: true}; + const expected = '{"nested":true}'; + const result = deterministicJSONStringify(data); + expect(result).to.equal(expected); + }); + + it('null', () => { + const data = {nested: null}; + const expected = '{"nested":null}'; + const result = deterministicJSONStringify(data); + expect(result).to.equal(expected); + }); + + it('undefined', () => { + const data = {nested: undefined}; + const expected = '{}'; + const result = deterministicJSONStringify(data); + expect(result).to.equal(expected); + }); + }); + }); + + describe('distinctValues', () => { + it('should return distinct primitive values', () => { + const arr = [1, 2, 2, 3, 'hello', 'hello', true, true]; + const expected = [1, 2, 3, 'hello', true]; + const result = distinctValues(arr); + expect(result).to.have.deep.members(expected); // Check for deep equality (order doesn't matter) + }); + + it('should treat deep equal objects as the same', () => { + const arr = [{a: 1}, {a: 1}, {b: 2}, {a: 1, b: 2}, {b: 2}, {b: 2, a: 1}]; + const expected = [{a: 1}, {b: 2}, {a: 1, b: 2}]; + const result = distinctValues(arr); + expect(result).to.have.deep.members(expected); + }); + + it('should handle nested objects', () => { + const arr = [ + {a: 1, nested: {c: 3, d: 4}}, + {a: 1, nested: {d: 4, c: 3}}, // Same as the first + {b: 2, nested: {d: 4}}, + ]; + const expected = [ + {a: 1, nested: {c: 3, d: 4}}, + {b: 2, nested: {d: 4}}, + ]; + const result = distinctValues(arr); + expect(result).to.have.deep.members(expected); + }); + + it('should handle mixed data types', () => { + const arr = [1, 'hello', {a: 1}, true, {a: 1}, 'hello']; + const expected = [1, 'hello', {a: 1}, true]; + const result = distinctValues(arr); + expect(result).to.have.deep.members(expected); + }); + }); +}); diff --git a/src/utils/DistinctValues.ts b/src/utils/DistinctValues.ts new file mode 100644 index 0000000000..3960f65de8 --- /dev/null +++ b/src/utils/DistinctValues.ts @@ -0,0 +1,60 @@ +/* + * Copyright 2024 Google LLC. + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Returns an array of distinct values. Order is not guaranteed. + * @param values - The values to filter. Should be JSON-serializable. + * @return - An array of distinct values. + */ +export function distinctValues(values: T[]): T[] { + const map = new Map(); + for (const value of values) { + map.set(deterministicJSONStringify(value), value); + } + return Array.from(map.values()); +} + +/** + * Returns a stringified version of the object with keys sorted. This is required to + * ensure that the stringified version of an object is deterministic independent of the + * order of keys. + * @param obj + * @return {string} + */ +export function deterministicJSONStringify(obj: unknown) { + return JSON.stringify(normalizeObject(obj)); +} + +function normalizeObject(obj: any) { + if ( + obj === undefined || + obj === null || + Array.isArray(obj) || + typeof obj !== 'object' + ) { + return obj; + } + + // Copy the original object key and values to a new object in sorted order. + const newObj: any = {}; + for (const key of Object.keys(obj).sort()) { + const value = obj[key]; + newObj[key] = normalizeObject(value); // Recursively sort nested objects + } + + return newObj; +} diff --git a/tests/browsing_context/test_create.py b/tests/browsing_context/test_create.py index fa805bc42f..b26f52885d 100644 --- a/tests/browsing_context/test_create.py +++ b/tests/browsing_context/test_create.py @@ -14,8 +14,9 @@ # limitations under the License. import pytest from anys import ANY_DICT, ANY_STR -from test_helpers import (ANY_TIMESTAMP, execute_command, get_tree, goto_url, - read_JSON_message, send_JSON_command, subscribe) +from test_helpers import (ANY_TIMESTAMP, AnyExtending, execute_command, + get_tree, goto_url, read_JSON_message, + send_JSON_command, subscribe) @pytest.mark.asyncio @@ -293,3 +294,55 @@ async def test_browsingContext_create_withUserContext(websocket, type): 'children': [], 'parent': None } + + +@pytest.mark.asyncio +@pytest.mark.parametrize("type", ["window", "tab"]) +@pytest.mark.parametrize("global_subscription", [True, False]) +async def test_browsingContext_subscribe_to_contextCreated_emits_for_existing( + websocket, type, context_id, another_context_id, global_subscription): + subscribe_command_id = await send_JSON_command( + websocket, + { + "method": "session.subscribe", + "params": { + # Subscribe to a domain and to a specific event twice. The + # events should not be duplicated. + "events": [ + "browsingContext", "browsingContext.contextCreated", + "browsingContext.contextCreated" + ], + # Missing "contexts" means global subscription. + **({} if global_subscription else { + "contexts": [another_context_id] + }) + } + }) + + # In case of global subscription, the events should be emitted for all + # contexts. Otherwise, only for the subscribed ones. + if global_subscription: + resp = await read_JSON_message(websocket) + assert resp == AnyExtending({ + 'method': 'browsingContext.contextCreated', + 'params': { + 'context': context_id, + }, + 'type': 'event', + }) + + resp = await read_JSON_message(websocket) + assert resp == AnyExtending({ + 'method': 'browsingContext.contextCreated', + 'params': { + 'context': another_context_id, + }, + 'type': 'event', + }) + + resp = await read_JSON_message(websocket) + assert resp == { + 'id': subscribe_command_id, + 'type': 'success', + 'result': {} + } diff --git a/tests/session/test_subscription.py b/tests/session/test_subscription.py index 30e384f4cf..f7978ec865 100644 --- a/tests/session/test_subscription.py +++ b/tests/session/test_subscription.py @@ -130,7 +130,6 @@ async def test_subscribeWithContext_subscribesToEventsInNestedContext( @pytest.mark.asyncio -@pytest.mark.skip(reason="TODO: Fix this test") async def test_subscribeToNestedContext_subscribesToTopLevelContext( websocket, context_id, iframe_id): await subscribe(websocket, ["log.entryAdded"], [iframe_id]) @@ -389,7 +388,7 @@ async def test_subscribeToMultipleChannels_eventsReceivedInProperOrder( "channel": channel_2, "params": { "events": ["log.entryAdded"], - "cointext": context_id + "context": context_id } }) diff --git a/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/browsing_context/context_created/context_created.py.ini b/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/browsing_context/context_created/context_created.py.ini deleted file mode 100644 index 84da1ac1b5..0000000000 --- a/wpt-metadata/chromedriver/headful/webdriver/tests/bidi/browsing_context/context_created/context_created.py.ini +++ /dev/null @@ -1,6 +0,0 @@ -[context_created.py] - [test_existing_context[tab\]] - expected: FAIL - - [test_existing_context[window\]] - expected: FAIL diff --git a/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/browsing_context/context_created/context_created.py.ini b/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/browsing_context/context_created/context_created.py.ini deleted file mode 100644 index 84da1ac1b5..0000000000 --- a/wpt-metadata/chromedriver/headless/webdriver/tests/bidi/browsing_context/context_created/context_created.py.ini +++ /dev/null @@ -1,6 +0,0 @@ -[context_created.py] - [test_existing_context[tab\]] - expected: FAIL - - [test_existing_context[window\]] - expected: FAIL diff --git a/wpt-metadata/mapper/headless/webdriver/tests/bidi/browsing_context/context_created/context_created.py.ini b/wpt-metadata/mapper/headless/webdriver/tests/bidi/browsing_context/context_created/context_created.py.ini deleted file mode 100644 index 84da1ac1b5..0000000000 --- a/wpt-metadata/mapper/headless/webdriver/tests/bidi/browsing_context/context_created/context_created.py.ini +++ /dev/null @@ -1,6 +0,0 @@ -[context_created.py] - [test_existing_context[tab\]] - expected: FAIL - - [test_existing_context[window\]] - expected: FAIL