summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fledge/tentative/resources
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/fledge/tentative/resources')
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/additional-bids.py59
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/bidding-logic.sub.py126
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/decision-logic.sub.py4
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/fledge-util.sub.js125
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/permissions.py54
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/request-tracker.py2
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/trusted-scoring-signals.py4
7 files changed, 298 insertions, 76 deletions
diff --git a/testing/web-platform/tests/fledge/tentative/resources/additional-bids.py b/testing/web-platform/tests/fledge/tentative/resources/additional-bids.py
new file mode 100644
index 0000000000..060606b41d
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/resources/additional-bids.py
@@ -0,0 +1,59 @@
+"""Endpoint to return additional bids in the appropriate response header.
+
+Additional bids are returned using the "Ad-Auction-Additional-Bid" response
+header, as described at
+https://github.com/WICG/turtledove/blob/main/FLEDGE.md#63-http-response-headers.
+
+This script generates one of "Ad-Auction-Additional-Bid" response header for
+each additional bid provided in a url-encoded `additionalBids` query parameter.
+
+All requests to this endpoint requires a "Sec-Ad-Auction-Fetch" request header
+with a value of b"?1"; this entrypoint otherwise returns a 400 response.
+"""
+import json
+import base64
+
+import fledge.tentative.resources.fledge_http_server_util as fledge_http_server_util
+
+
+class BadRequestError(Exception):
+ pass
+
+
+def main(request, response):
+ try:
+ if fledge_http_server_util.handle_cors_headers_and_preflight(request, response):
+ return
+
+ # Verify that Sec-Ad-Auction-Fetch is present
+ if (request.headers.get("Sec-Ad-Auction-Fetch", default=b"").decode("utf-8") != "?1"):
+ raise BadRequestError("Sec-Ad-Auction-Fetch missing or unexpected value; expected '?1'")
+
+ # Return each signed additional bid in its own header
+ additional_bids = request.GET.get(b"additionalBids", default=b"").decode("utf-8")
+ if not additional_bids:
+ raise BadRequestError("Missing 'additionalBids' parameter")
+ for additional_bid in json.loads(additional_bids):
+ additional_bid_string = json.dumps(additional_bid)
+ auction_nonce = additional_bid.get("auctionNonce", None)
+ if not auction_nonce:
+ raise BadRequestError("Additional bid missing required 'auctionNonce' field")
+ signed_additional_bid = json.dumps({
+ "bid": additional_bid_string,
+ "signatures": []
+ })
+ additional_bid_header_value = (auction_nonce.encode("utf-8") + b":" +
+ base64.b64encode(signed_additional_bid.encode("utf-8")))
+ response.headers.append(b"Ad-Auction-Additional-Bid", additional_bid_header_value)
+
+ response.status = (200, b"OK")
+ response.headers.set(b"Content-Type", b"text/plain")
+
+ except BadRequestError as error:
+ response.status = (400, b"Bad Request")
+ response.headers.set(b"Content-Type", b"text/plain")
+ response.content = str(error)
+
+ except Exception as exception:
+ response.status = (500, b"Internal Server Error")
+ response.content = str(exception)
diff --git a/testing/web-platform/tests/fledge/tentative/resources/bidding-logic.sub.py b/testing/web-platform/tests/fledge/tentative/resources/bidding-logic.sub.py
index 707e37f36b..e17f2c2c75 100644
--- a/testing/web-platform/tests/fledge/tentative/resources/bidding-logic.sub.py
+++ b/testing/web-platform/tests/fledge/tentative/resources/bidding-logic.sub.py
@@ -1,71 +1,83 @@
from pathlib import Path
+from fledge.tentative.resources import fledge_http_server_util
+
# General bidding logic script. Depending on query parameters, it can
-# simulate a variety of network errors, and its generateBid() and
-# reportWin() functions can have arbitrary Javascript code injected
-# in them. generateBid() will by default return a bid of 9 for the
+# simulate a variety of network errors, and its generateBid(), reportWin(),
+# and reportAdditionalBidWin() functions can have arbitrary Javascript code
+# injected in them. generateBid() will by default return a bid of 9 for the
# first ad.
def main(request, response):
- error = request.GET.first(b"error", None)
+ if fledge_http_server_util.handle_cors_headers_and_preflight(request, response):
+ return
+
+ error = request.GET.first(b"error", None)
- if error == b"close-connection":
- # Close connection without writing anything, to simulate a network
- # error. The write call is needed to avoid writing the default headers.
- response.writer.write("")
- response.close_connection = True
- return
+ if error == b"close-connection":
+ # Close connection without writing anything, to simulate a network
+ # error. The write call is needed to avoid writing the default headers.
+ response.writer.write("")
+ response.close_connection = True
+ return
- if error == b"http-error":
- response.status = (404, b"OK")
- else:
- response.status = (200, b"OK")
+ if error == b"http-error":
+ response.status = (404, b"OK")
+ else:
+ response.status = (200, b"OK")
- if error == b"wrong-content-type":
- response.headers.set(b"Content-Type", b"application/json")
- elif error != b"no-content-type":
- response.headers.set(b"Content-Type", b"application/javascript")
+ if error == b"wrong-content-type":
+ response.headers.set(b"Content-Type", b"application/json")
+ elif error != b"no-content-type":
+ response.headers.set(b"Content-Type", b"application/javascript")
- if error == b"bad-allow-fledge":
- response.headers.set(b"Ad-Auction-Allowed", b"sometimes")
- elif error == b"fledge-not-allowed":
- response.headers.set(b"Ad-Auction-Allowed", b"false")
- elif error != b"no-allow-fledge":
- response.headers.set(b"Ad-Auction-Allowed", b"true")
+ if error == b"bad-allow-fledge":
+ response.headers.set(b"Ad-Auction-Allowed", b"sometimes")
+ elif error == b"fledge-not-allowed":
+ response.headers.set(b"Ad-Auction-Allowed", b"false")
+ elif error != b"no-allow-fledge":
+ response.headers.set(b"Ad-Auction-Allowed", b"true")
- if error == b"no-body":
- return b''
+ if error == b"no-body":
+ return b''
- body = (Path(__file__).parent.resolve() / 'worklet-helpers.js').read_text().encode("ASCII")
- if error != b"no-generateBid":
- # Use bid query param if present. Otherwise, use a bid of 9.
- bid = (request.GET.first(b"bid", None) or b"9").decode("ASCII")
+ body = (Path(__file__).parent.resolve() / 'worklet-helpers.js').read_text().encode("ASCII")
+ if error != b"no-generateBid":
+ # Use bid query param if present. Otherwise, use a bid of 9.
+ bid = (request.GET.first(b"bid", None) or b"9").decode("ASCII")
- bidCurrency = ""
- bidCurrencyParam = request.GET.first(b"bidCurrency", None)
- if bidCurrencyParam != None:
- bidCurrency = "bidCurrency: '" + bidCurrencyParam.decode("ASCII") + "',"
+ bidCurrency = ""
+ bidCurrencyParam = request.GET.first(b"bidCurrency", None)
+ if bidCurrencyParam != None:
+ bidCurrency = "bidCurrency: '" + bidCurrencyParam.decode("ASCII") + "',"
- allowComponentAuction = ""
- allowComponentAuctionParam = request.GET.first(b"allowComponentAuction", None)
- if allowComponentAuctionParam != None:
- allowComponentAuction = f"allowComponentAuction: {allowComponentAuctionParam.decode('ASCII')},"
+ allowComponentAuction = ""
+ allowComponentAuctionParam = request.GET.first(b"allowComponentAuction", None)
+ if allowComponentAuctionParam != None:
+ allowComponentAuction = f"allowComponentAuction: {allowComponentAuctionParam.decode('ASCII')},"
- body += f"""
- function generateBid(interestGroup, auctionSignals, perBuyerSignals,
- trustedBiddingSignals, browserSignals,
- directFromSellerSignals) {{
- {{{{GET[generateBid]}}}};
- return {{
- bid: {bid},
- {bidCurrency}
- {allowComponentAuction}
- render: interestGroup.ads[0].renderURL
- }};
- }}""".encode()
- if error != b"no-reportWin":
- body += b"""
- function reportWin(auctionSignals, perBuyerSignals, sellerSignals,
- browserSignals, directFromSellerSignals) {
- {{GET[reportWin]}};
- }"""
- return body
+ body += f"""
+ function generateBid(interestGroup, auctionSignals, perBuyerSignals,
+ trustedBiddingSignals, browserSignals,
+ directFromSellerSignals) {{
+ {{{{GET[generateBid]}}}};
+ return {{
+ bid: {bid},
+ {bidCurrency}
+ {allowComponentAuction}
+ render: interestGroup.ads[0].renderURL
+ }};
+ }}""".encode()
+ if error != b"no-reportWin":
+ body += b"""
+ function reportWin(auctionSignals, perBuyerSignals, sellerSignals,
+ browserSignals, directFromSellerSignals) {
+ {{GET[reportWin]}};
+ }"""
+ if error != b"no-reportAdditionalBidWin":
+ body += b"""
+ function reportAdditionalBidWin(auctionSignals, perBuyerSignals,
+ sellerSignals, browserSignals,
+ directFromSellerSignals) {
+ {{GET[reportAdditionalBidWin]}};
+ }"""
+ return body
diff --git a/testing/web-platform/tests/fledge/tentative/resources/decision-logic.sub.py b/testing/web-platform/tests/fledge/tentative/resources/decision-logic.sub.py
index 78d459e3f9..3a23f98162 100644
--- a/testing/web-platform/tests/fledge/tentative/resources/decision-logic.sub.py
+++ b/testing/web-platform/tests/fledge/tentative/resources/decision-logic.sub.py
@@ -43,8 +43,8 @@ def main(request, response):
// Don't bid on interest group with the wrong uuid. This is to prevent
// left over interest groups from other tests from affecting auction
// results.
- if (!browserSignals.renderUrl.endsWith('uuid={{GET[uuid]}}') &&
- !browserSignals.renderUrl.includes('uuid={{GET[uuid]}}&')) {
+ if (!browserSignals.renderURL.endsWith('uuid={{GET[uuid]}}') &&
+ !browserSignals.renderURL.includes('uuid={{GET[uuid]}}&')) {
return 0;
}
diff --git a/testing/web-platform/tests/fledge/tentative/resources/fledge-util.sub.js b/testing/web-platform/tests/fledge/tentative/resources/fledge-util.sub.js
index 69573d4998..5819357e29 100644
--- a/testing/web-platform/tests/fledge/tentative/resources/fledge-util.sub.js
+++ b/testing/web-platform/tests/fledge/tentative/resources/fledge-util.sub.js
@@ -33,7 +33,7 @@ const OTHER_ORIGIN7 = 'https://{{hosts[alt][www]}}:{{ports[https][1]}}';
// on behavior of the script; it only serves to make the URL unique.
// `id` will always be the last query parameter.
function createTrackerURL(origin, uuid, dispatch, id = null) {
- let url = new URL(`${origin}${BASE_PATH}resources/request-tracker.py`);
+ let url = new URL(`${origin}${RESOURCE_PATH}request-tracker.py`);
let search = `uuid=${uuid}&dispatch=${dispatch}`;
if (id)
search += `&id=${id}`;
@@ -59,6 +59,10 @@ function createSellerReportURL(uuid, id = '1', origin = window.location.origin)
return createTrackerURL(origin, uuid, `track_get`, `seller_report_${id}`);
}
+function createHighestScoringOtherBidReportURL(uuid, highestScoringOtherBid) {
+ return createSellerReportURL(uuid) + '&highestScoringOtherBid=' + Math.round(highestScoringOtherBid);
+}
+
// Much like above ReportURL methods, except designed for beacons, which
// are expected to be POSTs.
function createBidderBeaconURL(uuid, id = '1', origin = window.location.origin) {
@@ -69,7 +73,7 @@ function createSellerBeaconURL(uuid, id = '1', origin = window.location.origin)
}
function createDirectFromSellerSignalsURL(origin = window.location.origin) {
- let url = new URL(`${origin}${BASE_PATH}resources/direct-from-seller-signals.py`);
+ let url = new URL(`${origin}${RESOURCE_PATH}direct-from-seller-signals.py`);
return url.toString();
}
@@ -80,7 +84,7 @@ function generateUuid(test) {
let uuid = token();
test.add_cleanup(async () => {
let response = await fetch(createCleanupURL(uuid),
- {credentials: 'omit', mode: 'cors'});
+ { credentials: 'omit', mode: 'cors' });
assert_equals(await response.text(), 'cleanup complete',
`Sever state cleanup failed`);
});
@@ -94,7 +98,7 @@ async function fetchTrackedData(uuid) {
let trackedRequestsURL = createTrackerURL(window.location.origin, uuid,
'tracked_data');
let response = await fetch(trackedRequestsURL,
- {credentials: 'omit', mode: 'cors'});
+ { credentials: 'omit', mode: 'cors' });
let trackedData = await response.json();
// Fail on fetch error.
@@ -118,23 +122,29 @@ async function fetchTrackedData(uuid) {
// Elements of `expectedRequests` should either be URLs, in the case of GET
// requests, or "<URL>, body: <body>" in the case of POST requests.
//
+// `filter` will be applied to the array of tracked requests.
+//
// If any other strings are received from the tracking script, or the tracker
// script reports an error, fails the test.
-async function waitForObservedRequests(uuid, expectedRequests) {
+async function waitForObservedRequests(uuid, expectedRequests, filter) {
// Sort array for easier comparison, as observed request order does not
// matter, and replace UUID to print consistent errors on failure.
- expectedRequests = expectedRequests.sort().map((url) => url.replace(uuid, '<uuid>'));
+ expectedRequests = expectedRequests.map((url) => url.replace(uuid, '<uuid>')).sort();
while (true) {
let trackedData = await fetchTrackedData(uuid);
// Clean up "trackedRequests" in same manner as "expectedRequests".
- let trackedRequests = trackedData.trackedRequests.sort().map(
- (url) => url.replace(uuid, '<uuid>'));
+ let trackedRequests = trackedData.trackedRequests.map(
+ (url) => url.replace(uuid, '<uuid>')).sort();
+
+ if (filter) {
+ trackedRequests = trackedRequests.filter(filter);
+ }
// If expected number of requests have been observed, compare with list of
// all expected requests and exit.
- if (trackedRequests.length == expectedRequests.length) {
+ if (trackedRequests.length >= expectedRequests.length) {
assert_array_equals(trackedRequests, expectedRequests);
break;
}
@@ -148,6 +158,16 @@ async function waitForObservedRequests(uuid, expectedRequests) {
}
}
+
+// Similar to waitForObservedRequests, but ignore forDebuggingOnly reports.
+async function waitForObservedRequestsIgnoreDebugOnlyReports(
+ uuid, expectedRequests) {
+ return waitForObservedRequests(
+ uuid,
+ expectedRequests,
+ request => !request.includes('forDebuggingOnly'));
+}
+
// Creates a bidding script with the provided code in the method bodies. The
// bidding script's generateBid() method will return a bid of 9 for the first
// ad, after the passed in code in the "generateBid" input argument has been
@@ -163,6 +183,8 @@ function createBiddingScriptURL(params = {}) {
url.searchParams.append('generateBid', params.generateBid);
if (params.reportWin != null)
url.searchParams.append('reportWin', params.reportWin);
+ if (params.reportAdditionalBidWin != null)
+ url.searchParams.append('reportAdditionalBidWin', params.reportAdditionalBidWin);
if (params.error != null)
url.searchParams.append('error', params.error);
if (params.bid != null)
@@ -254,7 +276,7 @@ async function joinInterestGroup(test, uuid, interestGroupOverrides = {},
await navigator.joinAdInterestGroup(interestGroup, durationSeconds);
test.add_cleanup(
- async () => {await navigator.leaveAdInterestGroup(interestGroup)});
+ async () => { await navigator.leaveAdInterestGroup(interestGroup) });
}
// Similar to joinInterestGroup, but leaves the interest group instead.
@@ -465,6 +487,24 @@ async function runReportTest(test, uuid, codeToInsert, expectedReportURLs,
await waitForObservedRequests(uuid, expectedReportURLs);
}
+async function runAdditionalBidTest(test, uuid, buyers, auctionNonce,
+ additionalBidsPromise,
+ highestScoringOtherBid,
+ winningAdditionalBidId) {
+ await runBasicFledgeAuctionAndNavigate(
+ test, uuid,
+ { interestGroupBuyers: buyers,
+ auctionNonce: auctionNonce,
+ additionalBids: additionalBidsPromise,
+ decisionLogicURL: createDecisionScriptURL(
+ uuid,
+ { reportResult: `sendReportTo("${createSellerReportURL(uuid)}&highestScoringOtherBid=" + Math.round(browserSignals.highestScoringOtherBid));` })});
+
+ await waitForObservedRequests(
+ uuid, [createHighestScoringOtherBidReportURL(uuid, highestScoringOtherBid),
+ createBidderReportURL(uuid, winningAdditionalBidId)]);
+}
+
// Runs "script" in "child_window" via an eval call. The "child_window" must
// have been created by calling "createFrame()" below. "param" is passed to the
// context "script" is run in, so can be used to pass objects that
@@ -504,7 +544,7 @@ async function runInFrame(test, child_window, script, param) {
// iframe or closes the window.
async function createFrame(test, origin, is_iframe = true, permissions = null) {
const frameUuid = generateUuid(test);
- const frameUrl =
+ const frameURL =
`${origin}${RESOURCE_PATH}subordinate-frame.sub.html?uuid=${frameUuid}`;
let promise = new Promise(function(resolve, reject) {
function WaitForMessage(event) {
@@ -523,7 +563,7 @@ async function createFrame(test, origin, is_iframe = true, permissions = null) {
let iframe = document.createElement('iframe');
if (permissions)
iframe.allow = permissions;
- iframe.src = frameUrl;
+ iframe.src = frameURL;
document.body.appendChild(iframe);
test.add_cleanup(async () => {
@@ -535,7 +575,7 @@ async function createFrame(test, origin, is_iframe = true, permissions = null) {
return iframe.contentWindow;
}
- let child_window = window.open(frameUrl);
+ let child_window = window.open(frameURL);
test.add_cleanup(async () => {
await runInFrame(test, child_window, "await test_instance.do_cleanup();");
child_window.close();
@@ -576,11 +616,23 @@ async function joinInterestGroupInTopLevelWindow(
let interestGroup = JSON.stringify(
createInterestGroupForOrigin(uuid, origin, interestGroupOverrides));
- let topLeveWindow = await createTopLevelWindow(test, origin);
- await runInFrame(test, topLeveWindow,
+ let topLevelWindow = await createTopLevelWindow(test, origin);
+ await runInFrame(test, topLevelWindow,
`await joinInterestGroup(test_instance, "${uuid}", ${interestGroup})`);
}
+// Opens a top-level window and calls joinCrossOriginInterestGroup() in it.
+async function joinCrossOriginInterestGroupInTopLevelWindow(
+ test, uuid, windowOrigin, interestGroupOrigin, interestGroupOverrides = {}) {
+ let interestGroup = JSON.stringify(
+ createInterestGroupForOrigin(uuid, interestGroupOrigin, interestGroupOverrides));
+
+ let topLevelWindow = await createTopLevelWindow(test, windowOrigin);
+ await runInFrame(test, topLevelWindow,
+ `await joinCrossOriginInterestGroup(
+ test_instance, "${uuid}", "${interestGroupOrigin}", ${interestGroup})`);
+}
+
// Fetch directFromSellerSignals from seller and check header
// 'Ad-Auction-Signals' is hidden from documents.
async function fetchDirectFromSellerSignals(headers_content, origin) {
@@ -643,3 +695,46 @@ function directFromSellerSignalsValidatorCode(uuid, expectedSellerSignals,
`sendReportTo("${createBidderReportURL(uuid)}");`,
};
}
+
+// Creates an additional bid with the given parameters. This additional bid
+// specifies a biddingLogicURL that provides an implementation of
+// reportAdditionalBidWin that triggers a sendReportTo() to the bidder report
+// URL of the winning additional bid. Additional bids are described in more
+// detail at
+// https://github.com/WICG/turtledove/blob/main/FLEDGE.md#6-additional-bids.
+function createAdditionalBid(uuid, auctionNonce, seller, buyer, interestGroupName, bidAmount,
+ additionalBidOverrides = {}) {
+ return {
+ interestGroup: {
+ name: interestGroupName,
+ biddingLogicURL: createBiddingScriptURL(
+ {
+ origin: buyer,
+ reportAdditionalBidWin: `sendReportTo("${createBidderReportURL(uuid, interestGroupName)}");`
+ }),
+ owner: buyer
+ },
+ bid: {
+ ad: ['metadata'],
+ bid: bidAmount,
+ render: createRenderURL(uuid)
+ },
+ auctionNonce: auctionNonce,
+ seller: seller,
+ ...additionalBidOverrides
+ }
+}
+
+// Fetch some number of additional bid from a seller and verify that the
+// 'Ad-Auction-Additional-Bid' header is not visible in this JavaScript context.
+// The `additionalBids` parameter is a list of additional bids.
+async function fetchAdditionalBids(seller, additionalBids) {
+ const url = new URL(`${seller}${RESOURCE_PATH}additional-bids.py`);
+ url.searchParams.append('additionalBids', JSON.stringify(additionalBids));
+ const response = await fetch(url.href, {adAuctionHeaders: true});
+
+ assert_equals(response.status, 200, 'Failed to fetch additional bid: ' + await response.text());
+ assert_false(
+ response.headers.has('Ad-Auction-Additional-Bid'),
+ 'Header "Ad-Auction-Additional-Bid" should not be available in JavaScript context.');
+}
diff --git a/testing/web-platform/tests/fledge/tentative/resources/permissions.py b/testing/web-platform/tests/fledge/tentative/resources/permissions.py
new file mode 100644
index 0000000000..eed93c4275
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/resources/permissions.py
@@ -0,0 +1,54 @@
+"""Methods for the interest group cross-origin permissions endpoint."""
+import json
+import re
+
+from fledge.tentative.resources import fledge_http_server_util
+
+SUBDOMAIN_WWW = 'www'
+SUBDOMAIN_WWW1 = 'www1'
+SUBDOMAIN_WWW2 = 'www2'
+SUBDOMAIN_FRENCH = 'élève'.encode('idna').decode()
+SUBDOMAIN_JAPANESE = '天気の良い日'.encode('idna').decode()
+ALL_SUBDOMAINS = [SUBDOMAIN_WWW, SUBDOMAIN_WWW1, SUBDOMAIN_WWW2,
+ SUBDOMAIN_FRENCH, SUBDOMAIN_JAPANESE]
+
+def get_permissions(request, response):
+ """Returns JSON object containing interest group cross-origin permissions.
+
+ The structure returned is described in more detail at
+ https://github.com/WICG/turtledove/blob/main/FLEDGE.md#13-permission-delegation.
+ This correctly handles requests issued in CORS mode.
+
+ This .well-known is fetched at the origin of the interest group's owner, and
+ specifies as a URL parameter the origin of frame that's attempting to join or
+ leave that interest group.
+
+ This is implemented such that the origin of the frame is ignored altogether,
+ and the determination of which operations are allowed depends strictly on the
+ origin of the interest group owner, and specifically on the subdomain of the
+ origin of the interest group owner. wptserve serves each of its two domains
+ at both the raw domain and each of five subdomains.
+
+ - www: disallows both join and leave
+ - www1: allows join, but not leave
+ - www2: allows leave, but not join
+ - 天気の良い日 / élève: allow both join and leave
+ - anything else (including no subdomain): returns a 404
+ """
+ if fledge_http_server_util.handle_cors_headers_and_preflight(request, response):
+ return
+
+ first_domain_label = re.search(r"[^.]*", request.url_parts.netloc).group(0)
+ if first_domain_label not in ALL_SUBDOMAINS:
+ response.status = (404, b"Not Found")
+ response.content = "Not Found"
+ return
+
+ response.status = (200, b"OK")
+ response.headers.set(b"Content-Type", b"application/json")
+ response.content = json.dumps({
+ "joinAdInterestGroup": first_domain_label in [
+ SUBDOMAIN_WWW1, SUBDOMAIN_FRENCH, SUBDOMAIN_JAPANESE],
+ "leaveAdInterestGroup": first_domain_label in [
+ SUBDOMAIN_WWW2, SUBDOMAIN_FRENCH, SUBDOMAIN_JAPANESE],
+ })
diff --git a/testing/web-platform/tests/fledge/tentative/resources/request-tracker.py b/testing/web-platform/tests/fledge/tentative/resources/request-tracker.py
index c449d2ab02..3514741f63 100644
--- a/testing/web-platform/tests/fledge/tentative/resources/request-tracker.py
+++ b/testing/web-platform/tests/fledge/tentative/resources/request-tracker.py
@@ -110,4 +110,6 @@ def simple_response(request, response, status_code, status_message, body,
content_type=b"text/plain"):
response.status = (status_code, status_message)
response.headers.set(b"Content-Type", content_type)
+ # Force refetch on reuse, so multiple requests to tracked URLs are all visible.
+ response.headers.set(b"Cache-control", b"no-store")
return body
diff --git a/testing/web-platform/tests/fledge/tentative/resources/trusted-scoring-signals.py b/testing/web-platform/tests/fledge/tentative/resources/trusted-scoring-signals.py
index 80488a5d6a..eccef5e762 100644
--- a/testing/web-platform/tests/fledge/tentative/resources/trusted-scoring-signals.py
+++ b/testing/web-platform/tests/fledge/tentative/resources/trusted-scoring-signals.py
@@ -15,7 +15,7 @@ def main(request, response):
renderUrls = None
adComponentRenderURLs = None
# List of {type: <render URL type>, urls: <render URL list>} pairs, where <render URL type> is
- # one of the two render URL dictionary keys used in the response ("renderUrls" or
+ # one of the two render URL dictionary keys used in the response ("renderURLs" or
# "adComponentRenderURLs"). May be of length 1 or 2, depending on whether there
# are any component URLs.
urlLists = []
@@ -36,7 +36,7 @@ def main(request, response):
continue
if pair[0] == "renderUrls" and renderUrls == None:
renderUrls = list(map(unquote_plus, pair[1].split(",")))
- urlLists.append({"type":"renderUrls", "urls":renderUrls})
+ urlLists.append({"type":"renderURLs", "urls":renderUrls})
continue
if pair[0] == "adComponentRenderUrls" and adComponentRenderURLs == None:
adComponentRenderURLs = list(map(unquote_plus, pair[1].split(",")))