| // META: script=/resources/testdriver.js |
| // META: script=/resources/testdriver-vendor.js |
| // META: script=/common/utils.js |
| // META: script=resources/fledge-util.sub.js |
| // META: timeout=long |
| |
| "use strict;" |
| |
| // Runs one auction at a time using `auctionConfigOverrides` until the auction |
| // has a winner. |
| async function runAuctionsUntilWinner(test, uuid, auctionConfigOverrides) { |
| fencedFrameConfig = null; |
| while (!fencedFrameConfig) { |
| fencedFrameConfig = |
| await runBasicFledgeAuction(test, uuid, auctionConfigOverrides); |
| } |
| return fencedFrameConfig; |
| } |
| |
| // This tests the case of ties. The winner of an auction is normally checked |
| // by these tests by checking a report sent when the winner is loaded in a fenced |
| // frame. Unfortunately, that requires a lot of navigations, which can be slow. |
| // |
| // So instead, run a multi-seller auction. The inner auction has two bidders, |
| // which both bid, and the seller gives them the same score. For the first |
| // auction, the top-level seller just accepts the only bid it sees, and then |
| // as usual, we navigate a fenced frame, to learn which bidder won. |
| // |
| // The for subsequent auctions, the nested component auction is identical, |
| // but the top-level auction rejects bids from the bidder that won the |
| // first auction. So if we have a winner, we know that the other bidder |
| // won the tie. Auctions are run in parallel until this happens. |
| // |
| // The interest groups use "group-by-origin" execution mode, to potentially |
| // allow the auctions run in parallel to complete faster. |
| promise_test(async test => { |
| const uuid = generateUuid(test); |
| |
| // Use different report URLs for each interest group, to identify |
| // which interest group has won an auction. |
| let reportURLs = [createBidderReportURL(uuid, /*id=*/'1'), |
| createBidderReportURL(uuid, /*id=*/'2')]; |
| |
| // Use different ad URLs for each auction. These need to be distinct |
| // so that the top-level seller can check the URL to check if the |
| // winning bid from the component auction has already won an |
| // auction. |
| let adURLs = [createRenderURL(uuid), |
| createRenderURL(uuid, /*script=*/';')]; |
| |
| await Promise.all( |
| [ joinInterestGroup( |
| test, uuid, |
| { name: 'group 1', |
| ads: [{ renderURL: adURLs[0] }], |
| executionMode: 'group-by-origin', |
| biddingLogicURL: createBiddingScriptURL( |
| { allowComponentAuction: true, |
| reportWin: `sendReportTo("${reportURLs[0]}");`})}), |
| joinInterestGroup( |
| test, uuid, |
| { name: 'group 2', |
| ads: [{ renderURL: adURLs[1] }], |
| executionMode: 'group-by-origin', |
| biddingLogicURL: createBiddingScriptURL( |
| { allowComponentAuction: true, |
| reportWin: `sendReportTo("${reportURLs[1]}");`})}) |
| ] |
| ); |
| |
| let componentAuctionConfig = { |
| seller: window.location.origin, |
| decisionLogicURL: createDecisionScriptURL(uuid), |
| interestGroupBuyers: [window.location.origin] |
| }; |
| |
| let auctionConfigOverrides = { |
| decisionLogicURL: createDecisionScriptURL(uuid), |
| interestGroupBuyers: [], |
| componentAuctions: [componentAuctionConfig] |
| }; |
| |
| await runBasicFledgeAuctionAndNavigate(test, uuid, auctionConfigOverrides); |
| |
| // Waiting for the report URL of the winner should succeed, while waiting for |
| // the one of the loser should throw. Wait for both, see which succeeds, and |
| // set "winningAdURL" to the ad URL of the winner. |
| let winningAdURL = ''; |
| try { |
| await waitForObservedRequests(uuid, [reportURLs[0]]); |
| winningAdURL = adURLs[0]; |
| } catch (e) { |
| await waitForObservedRequests(uuid, [reportURLs[1]]); |
| winningAdURL = adURLs[1]; |
| } |
| |
| // Modify `auctionConfigOverrides` to only accept the ad from the interest |
| // group that didn't win the first auction. |
| auctionConfigOverrides.decisionLogicURL = |
| createDecisionScriptURL( |
| uuid, |
| {scoreAd: `if (browserSignals.renderURL === "${winningAdURL}") |
| return 0;`}); |
| |
| // Add an abort controller, so can cancel extra auctions. |
| let abortController = new AbortController(); |
| auctionConfigOverrides.signal = abortController.signal; |
| |
| // Run a bunch of auctions in parallel, until one has a winner. |
| let fencedFrameConfig = await Promise.any( |
| [ runAuctionsUntilWinner(test, uuid, auctionConfigOverrides), |
| runAuctionsUntilWinner(test, uuid, auctionConfigOverrides), |
| runAuctionsUntilWinner(test, uuid, auctionConfigOverrides), |
| runAuctionsUntilWinner(test, uuid, auctionConfigOverrides), |
| runAuctionsUntilWinner(test, uuid, auctionConfigOverrides), |
| runAuctionsUntilWinner(test, uuid, auctionConfigOverrides) |
| ] |
| ); |
| // Abort the other auctions. |
| abortController.abort('reason'); |
| |
| // Load the fencedFrameConfig in a fenced frame, and double-check that each |
| // interest group has won once. |
| createAndNavigateFencedFrame(test, fencedFrameConfig); |
| await waitForObservedRequests(uuid, [reportURLs[0], reportURLs[1]]); |
| }, 'runAdAuction tie.'); |