diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/payment-request | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/payment-request')
48 files changed, 3778 insertions, 0 deletions
diff --git a/testing/web-platform/tests/payment-request/META.yml b/testing/web-platform/tests/payment-request/META.yml new file mode 100644 index 0000000000..1dbe3e5d7e --- /dev/null +++ b/testing/web-platform/tests/payment-request/META.yml @@ -0,0 +1,7 @@ +spec: https://w3c.github.io/payment-request/ +suggested_reviewers: + - marcoscaceres + - rsolomakhin + - zouhir + - romandev + - aestes diff --git a/testing/web-platform/tests/payment-request/PaymentMethodChangeEvent/methodDetails-attribute.https.html b/testing/web-platform/tests/payment-request/PaymentMethodChangeEvent/methodDetails-attribute.https.html new file mode 100644 index 0000000000..feaaef66ad --- /dev/null +++ b/testing/web-platform/tests/payment-request/PaymentMethodChangeEvent/methodDetails-attribute.https.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test for PaymentMethodChangeEvent.methodDetails attribute</title> +<link rel="help" href="https://w3c.github.io/browser-payment-api/#dom-paymentmethodchangeevent-methoddetails"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +"use strict"; +test(() => { + const methodDetails = { + test: "pass" + } + const event = new PaymentMethodChangeEvent("test", { + methodName: "wpt-test", + methodDetails + }); + assert_idl_attribute(event, "methodDetails"); + const { test } = event.methodDetails; + assert_equals(test, "pass"); +}, "Must have a methodDetails IDL attribute, which is initialized with to the methodName dictionary value"); + +test(() => { + const event = new PaymentMethodChangeEvent("test"); + assert_equals(event.methodDetails, null, "methodDetails attribute must initialize to null"); + + const event2 = new PaymentMethodChangeEvent("test", { methodName: "basic-card" }); + assert_equals(event2.methodDetails, null, "methodDetails attribute must initialize to null"); + + const event3 = new PaymentMethodChangeEvent("test", {}); + assert_equals(event2.methodDetails, null, "methodDetails attribute must initialize to null"); +}, "The methodDetails member defaults to null"); +</script> diff --git a/testing/web-platform/tests/payment-request/PaymentMethodChangeEvent/methodName-attribute.https.html b/testing/web-platform/tests/payment-request/PaymentMethodChangeEvent/methodName-attribute.https.html new file mode 100644 index 0000000000..176638c785 --- /dev/null +++ b/testing/web-platform/tests/payment-request/PaymentMethodChangeEvent/methodName-attribute.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test for PaymentMethodChangeEvent.methodName attribute</title> +<link rel="help" href="https://w3c.github.io/browser-payment-api/#dom-paymentmethodchangeevent-src"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +"use strict"; +test(() => { + const event = new PaymentMethodChangeEvent("test", { + methodName: "wpt-test", + }); + assert_idl_attribute(event, "methodName"); + const { methodName } = event; + assert_equals(methodName, "wpt-test"); +}, "Must have a methodName IDL attribute, which is initialized with to the methodName dictionary value"); + +test(() => { + const event = new PaymentMethodChangeEvent("test"); + assert_equals(event.methodName, "", "methodName attribute must initialize to empty string"); + + const event2 = new PaymentMethodChangeEvent("test", { methodDetails: {} }); + assert_equals(event2.methodName, "", "methodName attribute must initialize to empty string"); + + const event3 = new PaymentMethodChangeEvent("test", {}); + assert_equals(event3.methodName, "", "methodName attribute must initialize to empty string"); +}, "When no dictionary is passed, the methodName member defaults to the empty string"); +</script> diff --git a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/constructor.http.html b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/constructor.http.html new file mode 100644 index 0000000000..db7765f7bf --- /dev/null +++ b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/constructor.http.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). --> +<meta charset="utf-8"> +<title>Test for PaymentRequestUpdateEvent Constructor (insecure)</title> +<link rel="help" href="https://w3c.github.io/browser-payment-api/#paymentrequestupdateevent-interface"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(() => { + assert_false("PaymentRequestUpdateEvent" in Window); +}, "PaymentRequestUpdateEvent constructor must not be exposed in insecure context"); +</script> diff --git a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/constructor.https.html b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/constructor.https.html new file mode 100644 index 0000000000..3de0469e9c --- /dev/null +++ b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/constructor.https.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). --> +<meta charset="utf-8"> +<title>Test for PaymentRequestUpdateEvent Constructor</title> +<link rel="help" href="https://w3c.github.io/browser-payment-api/#constructor"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +const examplePay = Object.freeze({ supportedMethods: "https://example.com/pay" }); +const defaultMethods = Object.freeze([examplePay]); +const defaultDetails = Object.freeze({ + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, +}); + +test(() => { + try { + new PaymentRequestUpdateEvent("test"); + } catch (err) { + assert_unreached(`Unexpected exception: ${err.message}`); + } +}, "PaymentRequestUpdateEvent can be constructed in secure-context"); + +test(() => { + const ev = new PaymentRequestUpdateEvent("test", { + bubbles: true, + cancelable: true, + composed: true, + }); + assert_false(ev.isTrusted, "constructed in script, so not be trusted"); + assert_true(ev.bubbles, "set by EventInitDict"); + assert_true(ev.cancelable, "set by EventInitDict"); + assert_true(ev.composed, "set by EventInitDict"); + assert_equals(ev.target, null, "initially null"); + assert_equals(ev.type, "test"); +}, "PaymentRequestUpdateEvent can be constructed with an EventInitDict, even if not trusted"); + +test(() => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const ev = new PaymentRequestUpdateEvent("test"); + request.addEventListener("test", evt => { + assert_equals(ev, evt); + }); + request.dispatchEvent(ev); +}, "PaymentRequestUpdateEvent can be dispatched, even if not trusted"); + +</script> diff --git a/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updatewith-method.https.html b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updatewith-method.https.html new file mode 100644 index 0000000000..9a60fe7a4c --- /dev/null +++ b/testing/web-platform/tests/payment-request/PaymentRequestUpdateEvent/updatewith-method.https.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). --> +<meta charset="utf-8"> +<title>Test for PaymentRequestUpdateEvent's updateWith() method</title> +<link rel="help" href="https://w3c.github.io/browser-payment-api/#updatewith-method"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +const examplePay = Object.freeze({ supportedMethods: "https://example.com/pay" }); +const defaultMethods = Object.freeze([examplePay]); +const defaultDetails = Object.freeze({ + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, +}); + +test(() => { + // Smoke test - checks target is set as expected + const request = new PaymentRequest(defaultMethods, defaultDetails); + const ev = new PaymentRequestUpdateEvent("test"); + request.dispatchEvent(ev); + assert_equals(ev.target, request, "The request and the target at the same"); +}, "Let target be the request which is dispatching the event."); + +// Github issue: https://github.com/w3c/browser-payment-api/issues/546 +test(() => { + const untrustedEvents = [ + new PaymentRequestUpdateEvent("just a test") + ].forEach(ev => { + assert_throws_dom( + "InvalidStateError", + () => { + ev.updateWith(Promise.resolve()); + }, + `untrusted event of type "${ev.type}" must throw "InvalidStateError"` + ); + }); +}, `Calling .updateWith() with an undispatched untrusted event throws "InvalidStateError"`); + +// Github issue: https://github.com/w3c/browser-payment-api/issues/546 +test(() => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const untrustedEvents = [ + new PaymentRequestUpdateEvent("just a test") + ].map(ev => { + request.dispatchEvent(ev); // set .target and dispatch flag + // unstrusted event. + assert_throws_dom( + "InvalidStateError", + () => { + ev.updateWith(Promise.resolve()) + }, + `untrusted event of type "${ev.type}" must throw "InvalidStateError"` + ); + }); +}, `Calling .updateWith() with a dispatched, untrusted event, throws "InvalidStateError"`); + +</script> diff --git a/testing/web-platform/tests/payment-request/PaymentValidationErrors/retry-shows-error-member-manual.https.html b/testing/web-platform/tests/payment-request/PaymentValidationErrors/retry-shows-error-member-manual.https.html new file mode 100644 index 0000000000..9135520cd7 --- /dev/null +++ b/testing/web-platform/tests/payment-request/PaymentValidationErrors/retry-shows-error-member-manual.https.html @@ -0,0 +1,50 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentvalidationerrors-error"> +<title> + PaymentValidationErrors' `error` member +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../payment-response/helpers.js"></script> +<script> +function retryShowsErrorMember(button) { + button.disabled = true; + promise_test(async t => { + const { response } = await getPaymentRequestResponse(); + await response.retry({ error: "PASS" }); + await response.complete("success"); + }, button.textContent.trim()); +} +</script> +<h2> + Manual Test for PaymentValidationErrors's `error` member - Please run in order! +</h2> +<p> + Click on each button in sequence from top to bottom without refreshing the page. + Each button will bring up the Payment Request UI window. +</p> +<p> + When presented with the payment sheet, use any card and select to "Pay". + You will be asked to retry the payment and an error should be shown somewhere + in the UI. The expected error string is described in each individual test. + If you see the error, hit "Pay" again. If you don't see the error, + abort the payment request by hitting "esc" - which means that particular test + has failed. +</p> +<ol> + <li> + <button onclick="retryShowsErrorMember(this);"> + The payment sheet shows the error "PASS" somewhere in the UI. + </button> + </li> + <li> + <button onclick="done();"> + Done! + </button> + </li> +</ol> +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">owners</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/PaymentValidationErrors/retry-shows-payer-member-manual.https.html b/testing/web-platform/tests/payment-request/PaymentValidationErrors/retry-shows-payer-member-manual.https.html new file mode 100644 index 0000000000..f8115e69ec --- /dev/null +++ b/testing/web-platform/tests/payment-request/PaymentValidationErrors/retry-shows-payer-member-manual.https.html @@ -0,0 +1,65 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentvalidationerrors-payer"> +<title> + PaymentValidationErrors' `payer` member +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="../payment-response/helpers.js"></script> +<script> +function retryShowsPayerMember(button, error) { + button.disabled = true; + promise_test(async t => { + const options = { + requestPayerName: true, + requestPayerEmail: true, + requestPayerPhone: true, + } + const { response } = await getPaymentRequestResponse(options); + await response.retry({ payer: error }); + await response.complete("success"); + }, button.textContent.trim()); +} +</script> +<h2> + Manual Test for PaymentValidationErrors' `payer` member - Please run in order! +</h2> +<p> + Click on each button in sequence from top to bottom without refreshing the page. + Each button will bring up the Payment Request UI window. +</p> +<p> + When presented with the payment sheet, use any card and select to "Pay". + You will be asked to retry the payment and an error should be shown somewhere + in the UI. The expected error string is described in each individual test. + If you see the error, hit "Pay" again. If you don't see the error, + abort the payment request by hitting "esc" - which means that particular test + has failed. +</p> +<ol> + <li> + <button onclick="retryShowsPayerMember(this, { email: 'EMAIL ERROR' });"> + The payment sheet shows "EMAIL ERROR" for the payer's email. + </button> + </li> + <li> + <button onclick="retryShowsPayerMember(this, { name: 'NAME ERROR' });"> + The payment sheet shows "NAME ERROR" for the payer's name. + </button> + </li> + <li> + <button onclick="retryShowsPayerMember(this, { phone: 'PHONE ERROR' });"> + The payment sheet shows "PHONE ERROR" for the payer's phone number. + </button> + </li> + <li> + <button onclick="done();"> + Done! + </button> + </li> +</ol> +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">owners</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/constructor_convert_method_data.https.html b/testing/web-platform/tests/payment-request/constructor_convert_method_data.https.html new file mode 100644 index 0000000000..f4a9a721d0 --- /dev/null +++ b/testing/web-platform/tests/payment-request/constructor_convert_method_data.https.html @@ -0,0 +1,73 @@ +<!DOCTYPE html> <meta charset="utf-8" /> +<title>Validates PaymentMethodData's data member during construction</title> +<link + rel="help" + href="https://w3c.github.io/browser-payment-api/#constructor" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + const details = { + total: { + label: "Total", + amount: { + currency: "USD", + value: "0.00", + }, + }, + }; + + test(() => { + new PaymentRequest([{ supportedMethods: "basic-card" }], details); + new PaymentRequest( + [{ supportedMethods: "https://apple.com/apple-pay" }], + details + ); + }, "Smoke test."); + + const knownPMIs = ["basic-card", "https://apple.com/apple-pay"]; + const unknownPMIs = ["fake-pmi", "https://does-not.exist"]; + + promise_test(async t => { + for (const supportedMethods of [].concat(knownPMIs).concat(unknownPMIs)) { + const method = { supportedMethods }; + const request = new PaymentRequest([method], details); + assert_throws_js( + TypeError, + () => { + const badMethod = Object.assign( + {}, + method, + { data: 123 } // <- this will throw + ); + new PaymentRequest([badMethod], details); + }, + "PaymentMethodData.data can't be converted to an Object." + ); + } + }, "Tries to convert data member during Payment Request construction, irrespective of PMI."); + + promise_test(async t => { + for (const supportedMethods of knownPMIs) { + const method = { supportedMethods }; + const request = new PaymentRequest([method], details); + + // Only check the PMIs that are actually supported + if (!(await request.canMakePayment())) continue; + + assert_throws_js( + TypeError, + () => { + const badMethod = Object.assign( + {}, + method, + /* This is invalid in both Apple Pay and Basic Card */ + { data: { supportedNetworks: "this will throw" } } + ); + new PaymentRequest([badMethod], details); + }, + "PaymentMethodData.data is invalid." + ); + } + }, "Converts PaymentMethodData's data to mandated IDL type during PaymentRequest construction."); +</script> diff --git a/testing/web-platform/tests/payment-request/delegate-request.https.sub.html b/testing/web-platform/tests/payment-request/delegate-request.https.sub.html new file mode 100644 index 0000000000..988550036c --- /dev/null +++ b/testing/web-platform/tests/payment-request/delegate-request.https.sub.html @@ -0,0 +1,81 @@ +<!DOCTYPE html> +<title>Payment request delegation test</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> + +<div> + Verifies that PaymentRequest.show() call from a cross-origin subframe without user activation + works if and only if the top frame has user activation and it delegates the capability to the + subframe. +</div> + +<iframe allow="payment" width="300px" height="50px" + src="https://{{hosts[alt][www]}}:{{ports[https][0]}}/payment-request/resources/delegate-request-subframe.sub.html"> +</iframe> + +<script> + // Returns a |Promise| that gets resolved with |event.data| when |window| + // receives from |source| a "message" event whose |event.data.type| matches the string + // |message_data_type|. + function getMessageData(message_data_type, source) { + return new Promise(resolve => { + function waitAndRemove(e) { + if (e.source != source || !e.data || e.data.type != message_data_type) + return; + window.removeEventListener("message", waitAndRemove); + resolve(e.data); + } + window.addEventListener("message", waitAndRemove); + }); + } + + promise_setup(async () => { + // Make sure the iframe has loaded. + await getMessageData("subframe-loaded", frames[0]); + }); + + const target_origin = "https://{{hosts[alt][www]}}:{{ports[https][0]}}"; + const request = {"type": "make-payment-request"}; + + promise_test(async () => { + let result_promise = getMessageData("result", frames[0]); + frames[0].postMessage(request, {targetOrigin: target_origin}); + let data = await result_promise; + + assert_equals(data.result, "failure"); + }, "Payment-request from a subframe fails without delegation when the top frame has no user activation"); + + promise_test(async () => { + let result_promise = getMessageData("result", frames[0]); + await test_driver.bless(); + frames[0].postMessage(request, {targetOrigin: target_origin}); + let data = await result_promise; + + assert_equals(data.result, "failure"); + }, "Payment-request from a subframe fails without delegation when the top frame has user activation"); + + promise_test(async () => { + let result_promise = getMessageData("result", frames[0]); + await test_driver.bless(); + frames[0].postMessage(request, {targetOrigin: target_origin, + delegate: "payment"}); + let data = await result_promise; + + assert_equals(data.result, "success"); + }, "Payment-request from a subframe succeeds with delegation when the top frame has user activation"); + + // This test must follow the successful test case above so that the user activation state there + // gets consumed. + promise_test(async () => { + let result_promise = getMessageData("result", frames[0]); + frames[0].postMessage(request, {targetOrigin: target_origin, + delegate: "payment"}); + let data = await result_promise; + + assert_equals(data.result, "failure"); + }, "Payment-request from a subframe fails with delegation when the top frame has no user activation"); + +</script> diff --git a/testing/web-platform/tests/payment-request/historical.https.html b/testing/web-platform/tests/payment-request/historical.https.html new file mode 100644 index 0000000000..aa183a58cd --- /dev/null +++ b/testing/web-platform/tests/payment-request/historical.https.html @@ -0,0 +1,45 @@ +<!doctype html> +<meta charset=utf-8> +<title>Historical Payment Request APIs</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> +[ + // https://github.com/w3c/browser-payment-api/pull/419 + ["paymentRequestID", "PaymentRequest"], + ["paymentRequestID", "PaymentResponse"], + + // https://github.com/w3c/browser-payment-api/pull/258 + ["careOf", "PaymentAddress"], + + // https://github.com/w3c/browser-payment-api/pull/219 + ["totalAmount", "PaymentResponse"], + + // https://github.com/w3c/browser-payment-api/pull/426 + ["paymentRequestId", "PaymentRequest"], + ["paymentRequestId", "PaymentResponse"], + + // https://github.com/w3c/payment-request/pull/765 + ["languageCode", "PaymentAddress"], + + //https://github.com/whatwg/html/pull/5915 + ["allowPaymentRequest", "HTMLIFrameElement"], + +].forEach(([member, interf]) => { + test(() => { + assert_false(member in window[interf].prototype); + }, member + ' in ' + interf); +}); + +// https://github.com/w3c/payment-request/pull/551 +test(() => { + const methods = []; + const expectedError = {name: 'toString should be called'}; + const unexpectedError = {name: 'sequence<DOMString> conversion is not allowed'}; + methods.toString = () => { throw expectedError; }; + Object.defineProperty(methods, '0', { get: () => { throw unexpectedError; } }); + assert_throws_exactly(expectedError, () => { + new PaymentRequest([{supportedMethods: methods}], {total: {label: 'bar', amount: {currency: 'BAZ', value: '0'}}}); + }); +}, 'supportedMethods must not support sequence<DOMString>'); +</script> diff --git a/testing/web-platform/tests/payment-request/idlharness.https.window.js b/testing/web-platform/tests/payment-request/idlharness.https.window.js new file mode 100644 index 0000000000..53ae47e892 --- /dev/null +++ b/testing/web-platform/tests/payment-request/idlharness.https.window.js @@ -0,0 +1,31 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js + +'use strict'; + +// https://w3c.github.io/payment-request/ + +idl_test( + ['payment-request'], + ['dom', 'html'], + idlArray => { + try { + const methods = [ + {supportedMethods: 'basic-card'}, + {supportedMethods: 'https://apple.com/apple-pay'}, + ]; + const amount = {currency: 'USD', value: '0'}; + const details = {total: {label: 'label', amount: amount} }; + window.paymentRequest = new PaymentRequest(methods, details); + } catch (e) { + // Surfaced below when paymentRequest is undefined. + } + + idlArray.add_objects({ + PaymentRequest: ['paymentRequest'], + PaymentMethodChangeEvent: ['new PaymentMethodChangeEvent("paymentmethodchange")'], + PaymentRequestUpdateEvent: ['new PaymentRequestUpdateEvent("paymentrequestupdate")'], + MerchantValidationEvent: ['new MerchantValidationEvent("merchantvalidation")'], + }); + } +); diff --git a/testing/web-platform/tests/payment-request/onpaymentmethodchange-attribute.https.html b/testing/web-platform/tests/payment-request/onpaymentmethodchange-attribute.https.html new file mode 100644 index 0000000000..f641bec4aa --- /dev/null +++ b/testing/web-platform/tests/payment-request/onpaymentmethodchange-attribute.https.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test for onpaymentmethodchange attribute</title> +<link rel="help" href="https://w3c.github.io/browser-payment-api/#onpaymentmethodchange-attribute"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +"use strict"; +const testMethod = Object.freeze({ supportedMethods: "not-a-real-method" }); +const applePay = Object.freeze({ + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + } +}); +const defaultMethods = Object.freeze([testMethod, applePay]); +const defaultDetails = Object.freeze({ + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, +}); + +test(() => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + assert_idl_attribute(request, "onpaymentmethodchange"); +}, "Must have a onpaymentmethodchange IDL attribute"); + +test(() => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const ev = new Event("paymentmethodchange"); + let didHandle = false; + request.onpaymentmethodchange = evt => { + assert_equals(ev, evt, "must be same event"); + didHandle = true; + }; + request.dispatchEvent(ev); + assert_true(didHandle, "event did not fire"); +}, `onpaymentmethodchange attribute is a generic handler for "paymentmethodchange"`); + +test(() => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const ev = new PaymentMethodChangeEvent("paymentmethodchange"); + let didHandle = false; + request.onpaymentmethodchange = evt => { + assert_equals(ev, evt, "must be same event"); + didHandle = true; + }; + request.dispatchEvent(ev); + assert_true(didHandle, "event did not fire"); +}, `onpaymentmethodchange attribute is a handler for PaymentMethodChangeEvent`); + +test(() => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const ev = new PaymentMethodChangeEvent("paymentmethodchange", { + methodName: "test" + }); + let didHandle = false; + let didListen = false; + request.onpaymentmethodchange = evt => { + assert_equals(ev, evt, "must be same event"); + didHandle = true; + }; + request.addEventListener("paymentmethodchange", evt => { + assert_equals(ev, evt, "must be same event"); + didListen = true; + }); + request.dispatchEvent(ev); + assert_true(didHandle, "onpaymentmethodchange did not receive the event"); + assert_true(didListen, "addEventListener did not receive the event"); +}, `onpaymentmethodchange attribute and listeners both work`); +</script> diff --git a/testing/web-platform/tests/payment-request/payment-is-showing.https.html b/testing/web-platform/tests/payment-request/payment-is-showing.https.html new file mode 100644 index 0000000000..a30029458f --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-is-showing.https.html @@ -0,0 +1,170 @@ +<!DOCTYPE html> <meta charset="utf-8" /> +<title>Test for PaymentRequest.show(optional promise) method</title> +<link + rel="help" + href="https://w3c.github.io/browser-payment-api/#dfn-payment-request-is-showing" +/> +<meta name="timeout" content="long" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<body> + <script> + "use strict"; + const applePayMethod = { + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + }, + }; + const methods = [{ supportedMethods: "basic-card" }, applePayMethod]; + const details = { + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, + }; + + /** + * Attaches an iframe to window.document. + * + * @param {String} src Optional resource URL to load. + * @returns {Promise} Resolves when the src loads. + */ + async function attachIframe(src = "./resources/blank.html") { + const iframe = document.createElement("iframe"); + iframe.allow = "payment"; + iframe.src = src; + document.body.appendChild(iframe); + await new Promise((resolve) => { + iframe.addEventListener("load", resolve, { once: true }); + }); + return iframe; + } + + function getShowPromiseFromContext(paymentRequest, context = this) { + return test_driver.bless( + "payment request show()", + () => { + return [paymentRequest.show()]; + }, + context + ); + } + + promise_test(async (t) => { + const request1 = new PaymentRequest(methods, details); + const request2 = new PaymentRequest(methods, details); + + // Sets the "payment-relevant browsing context's payment request is + // showing boolean" to true and then try to show a second payment sheet in + // the same window. The second show() should reject. + await test_driver.bless("payment request show()"); + const showPromise1 = request1.show(); + + await test_driver.bless("payment request show()"); + const showPromise2 = request2.show(); + + await promise_rejects_dom( + t, + "AbortError", + showPromise2, + "Attempting to show a second payment request must reject." + ); + + await request1.abort(); + await promise_rejects_dom( + t, + "AbortError", + showPromise1, + "request1 was aborted via .abort()" + ); + + // Finally, request2 should have been "closed", so trying to show + // it will again result in promise rejected with an InvalidStateError. + // See: https://github.com/w3c/payment-request/pull/821 + const rejectedPromise = request2.show(); + await promise_rejects_dom( + t, + "InvalidStateError", + rejectedPromise, + "Attempting to show a second payment request must reject." + ); + // Finally, we confirm that request2's returned promises are unique. + assert_not_equals( + showPromise2, + rejectedPromise, + "Returned Promises be unique" + ); + }, "The top browsing context can only show one payment sheet at a time."); + + promise_test(async (t) => { + const iframe = await attachIframe(); + const iframeWindow = iframe.contentWindow; + + // Payment requests + const windowRequest = new window.PaymentRequest(methods, details); + const iframeRequest = new iframeWindow.PaymentRequest(methods, details); + + // Let's get some blessed showPromises + // iframe sets "is showing boolean", ignore the returned promise. + const [iframePromise] = await getShowPromiseFromContext( + iframeRequest, + iframeWindow + ); + + // The top level window now tries to show() the payment request. + await test_driver.bless("payment request show()"); + const showPromise = windowRequest.show(); + + await promise_rejects_dom( + t, + "AbortError", + showPromise, + "iframe is already showing a payment request." + ); + + // Cleanup + await iframeRequest.abort(); + iframe.remove(); + }, "If an iframe shows a payment request, the top-level browsing context can't also show one."); + + promise_test(async (t) => { + const iframe = await attachIframe(); + const iframeWindow = iframe.contentWindow; + const iframeRequest = new iframeWindow.PaymentRequest(methods, details); + const [iframeShowPromise] = await getShowPromiseFromContext( + iframeRequest, + iframeWindow + ); + + // We navigate away, causing the payment sheet to close + // and the request is showing boolean to become false. + await new Promise((resolve) => { + iframe.onload = resolve; + iframe.src = "./resources/blank.html?test=123"; + }); + + iframe.remove(); + + // Now we should be ok to spin up a new payment request + const request = new window.PaymentRequest(methods, details); + const [showPromise] = await getShowPromiseFromContext(request); + await request.abort(); + await promise_rejects_dom( + t, + "AbortError", + showPromise, + "Normal abort." + ); + }, "Navigating an iframe as a nested browsing context sets 'payment request is showing boolean' to false."); + </script> +</body> diff --git a/testing/web-platform/tests/payment-request/payment-request-abort-method.https.html b/testing/web-platform/tests/payment-request/payment-request-abort-method.https.html new file mode 100644 index 0000000000..32b87970b4 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-abort-method.https.html @@ -0,0 +1,91 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>Test for PaymentRequest.abort() method</title> +<link rel="help" href="https://w3c.github.io/payment-request/#abort-method" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script> +"use strict"; +setup({ + // Ignore unhandled rejections resulting from .show()'s acceptPromise + // not being explicitly handled. + allow_uncaught_exception: true, + explicit_timeout: true, +}); +const basicCard = Object.freeze({ supportedMethods: "basic-card" }); +const applePay = Object.freeze({ + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + }, +}); +const defaultMethods = Object.freeze([basicCard, applePay]); +const defaultDetails = Object.freeze({ + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, +}); + +promise_test(async t => { + // request is in "created" state + const request = new PaymentRequest(defaultMethods, defaultDetails); + await promise_rejects_dom(t, "InvalidStateError", request.abort()); +}, `Throws if the promise [[state]] is not "interactive"`); + +promise_test(async t => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const promises = new Set([ + request.abort(), + request.abort(), + request.abort(), + ]); + assert_equals(promises.size, 3, "Must have three unique objects"); +}, "Calling abort() multiple times is always a new object."); + +promise_test(async t => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + await test_driver.bless("show payment request"); + const acceptPromise = request.show(); + await request.abort(); + await promise_rejects_dom(t, "AbortError", acceptPromise); + // As request is now "closed", trying to show it will fail + await test_driver.bless("show payment request"); + await promise_rejects_dom(t, "InvalidStateError", request.show()); +}, "The same request cannot be shown multiple times."); + +promise_test(async t => { + // request is in "created" state. + const request = new PaymentRequest(defaultMethods, defaultDetails); + await promise_rejects_dom(t, "InvalidStateError", request.abort()); + // Call it again, for good measure. + await promise_rejects_dom(t, "InvalidStateError", request.abort()); + // The request's state is "created", so let's show it + // which changes the state to "interactive.". + await test_driver.bless("show payment request"); + const acceptPromise = request.show(); + // Let's set request the state to "closed" by calling .abort() + await request.abort(); + // The request is now "closed", so... + await promise_rejects_dom(t, "InvalidStateError", request.abort()); + await promise_rejects_dom(t, "AbortError", acceptPromise); +}, "Aborting a request before it is shown doesn't prevent it from being shown later."); +</script> +<small> + If you find a buggy test, please + <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> and + tag one of the + <a + href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml" + >suggested reviewers</a + >. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-request-canmakepayment-method.https.html b/testing/web-platform/tests/payment-request/payment-request-canmakepayment-method.https.html new file mode 100644 index 0000000000..f02474de12 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-canmakepayment-method.https.html @@ -0,0 +1,120 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Tests for PaymentRequest.canMakePayment() method</title> +<link rel="help" href="https://w3c.github.io/payment-request/#canmakepayment-method"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src='/resources/testdriver-vendor.js'></script> +<script> +"use strict"; +const basicCard = Object.freeze({ supportedMethods: "basic-card" }); +const applePay = Object.freeze({ + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + } +}); +const defaultMethods = Object.freeze([basicCard, applePay]); +const defaultDetails = Object.freeze({ + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, +}); + +const unsupportedMethods = [ + { supportedMethods: "this-is-not-supported" }, + { supportedMethods: "https://not.supported" }, +]; + +promise_test(async t => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + assert_true( + await request.canMakePayment(), + "one of the methods should be supported" + ); +}, `If payment method identifier are supported, resolve promise with true.`); + +promise_test(async t => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + try { + assert_true( + await request.canMakePayment(), + `canMakePaymentPromise should be true` + ); + assert_true( + await request.canMakePayment(), + `canMakePaymentPromise should be true` + ); + } catch (err) { + assert_equals( + err.name, + "NotAllowedError", + "if it throws, then it must be a NotAllowedError." + ); + } +}, `If request.[[state]] is "created", then return a promise that resolves to true for known method.`); + +promise_test(async t => { + const noneSupported = new PaymentRequest( + unsupportedMethods, + defaultDetails + ).canMakePayment(); + assert_false(await noneSupported, `methods must not be supported`); +}, "All methods are unsupported"); + +promise_test(async t => { + const someSupported = new PaymentRequest( + [...unsupportedMethods, ...defaultMethods], + defaultDetails + ).canMakePayment(); + assert_true(await someSupported, `At least one method is expected to be supported.`); +}, `Mix of supported and unsupported methods, at least one method is supported.`); + +promise_test(async t => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const [acceptPromise, canMakePaymentPromise] = await test_driver.bless( + "show payment request", + () => { + const acceptPromise = request.show(); // Sets state to "interactive" + const canMakePaymentPromise = request.canMakePayment(); + return [acceptPromise, canMakePaymentPromise]; + }); + + await promise_rejects_dom(t, "InvalidStateError", canMakePaymentPromise); + request.abort(); + await promise_rejects_dom(t, "AbortError", acceptPromise); + + // The state should be "closed" + await promise_rejects_dom(t, "InvalidStateError", request.canMakePayment()); +}, 'If request.[[state]] is "interactive", then return a promise rejected with an "InvalidStateError" DOMException.'); + +promise_test(async t => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const [abortPromise, acceptPromise] = await test_driver.bless( + "show payment request", + () => { + const acceptPromise = request.show(); // Sets state to "interactive" + acceptPromise.catch(() => {}); // no-op, just to silence unhandled rejection in devtools. + const abortPromise = request.abort(); // Sets state to "closed" + return [abortPromise, acceptPromise]; + }); + + await abortPromise; + await promise_rejects_dom(t, "AbortError", acceptPromise); + await promise_rejects_dom(t, "InvalidStateError", request.canMakePayment()); +}, 'If request.[[state]] is "closed", then return a promise rejected with an "InvalidStateError" DOMException.'); +</script> + +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-request-constructor.https.sub.html b/testing/web-platform/tests/payment-request/payment-request-constructor.https.sub.html new file mode 100644 index 0000000000..c1ecc22583 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-constructor.https.sub.html @@ -0,0 +1,477 @@ +<!DOCTYPE html> +<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). --> +<meta charset="utf-8"> +<title>Test for PaymentRequest Constructor</title> +<link rel="help" href="https://w3c.github.io/browser-payment-api/#constructor"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +"use strict"; +const testMethod = Object.freeze({ + supportedMethods: "https://{{domains[nonexistent]}}/payment-request", +}); +const defaultMethods = Object.freeze([testMethod]); +const defaultAmount = Object.freeze({ + currency: "USD", + value: "1.0", +}); +const defaultNumberAmount = Object.freeze({ + currency: "USD", + value: 1.0, +}); +const defaultTotal = Object.freeze({ + label: "Default Total", + amount: defaultAmount, +}); +const defaultNumberTotal = Object.freeze({ + label: "Default Number Total", + amount: defaultNumberAmount, +}); +const defaultDetails = Object.freeze({ + total: defaultTotal, + displayItems: [ + { + label: "Default Display Item", + amount: defaultAmount, + }, + ], +}); +const defaultNumberDetails = Object.freeze({ + total: defaultNumberTotal, + displayItems: [ + { + label: "Default Display Item", + amount: defaultNumberAmount, + }, + ], +}); + +// Avoid false positives, this should always pass +function smokeTest() { + new PaymentRequest(defaultMethods, defaultDetails); + new PaymentRequest(defaultMethods, defaultNumberDetails); +} +test(() => { + smokeTest(); + const request = new PaymentRequest(defaultMethods, defaultDetails); + assert_true(Boolean(request.id), "must be some truthy value"); +}, "If details.id is missing, assign an identifier"); + +test(() => { + smokeTest(); + const request1 = new PaymentRequest(defaultMethods, defaultDetails); + const request2 = new PaymentRequest(defaultMethods, defaultDetails); + assert_not_equals(request1.id, request2.id, "UA generated ID must be unique"); + const seen = new Set(); + // Let's try creating lots of requests, and make sure they are all unique + for (let i = 0; i < 1024; i++) { + const request = new PaymentRequest(defaultMethods, defaultDetails); + assert_false( + seen.has(request.id), + `UA generated ID must be unique, but got duplicate! (${request.id})` + ); + seen.add(request.id); + } +}, "If details.id is missing, assign a unique identifier"); + +test(() => { + smokeTest(); + const newDetails = Object.assign({}, defaultDetails, { id: "test123" }); + const request1 = new PaymentRequest(defaultMethods, newDetails); + const request2 = new PaymentRequest(defaultMethods, newDetails); + assert_equals(request1.id, newDetails.id, `id must be ${newDetails.id}`); + assert_equals(request2.id, newDetails.id, `id must be ${newDetails.id}`); + assert_equals(request1.id, request2.id, "ids need to be the same"); +}, "If the same id is provided, then use it"); + +test(() => { + smokeTest(); + const newDetails = Object.assign({}, defaultDetails, { + id: "".padStart(1024, "a"), + }); + const request = new PaymentRequest(defaultMethods, newDetails); + assert_equals( + request.id, + newDetails.id, + `id must be provided value, even if very long and contain spaces` + ); +}, "Use ids even if they are strange"); + +test(() => { + smokeTest(); + const request = new PaymentRequest( + defaultMethods, + Object.assign({}, defaultDetails, { id: "foo" }) + ); + assert_equals(request.id, "foo"); +}, "Use provided request ID"); + +test(() => { + smokeTest(); + assert_throws_js(TypeError, () => new PaymentRequest([], defaultDetails)); +}, "If the length of the methodData sequence is zero, then throw a TypeError"); + +test(() => { + smokeTest(); + const duplicateMethods = [ + { + supportedMethods: "https://{{domains[nonexistent]}}/payment-request", + }, + { + supportedMethods: "https://{{domains[nonexistent]}}/payment-request", + }, + ]; + assert_throws_js(RangeError, () => new PaymentRequest(duplicateMethods, defaultDetails)); +}, "If payment method is duplicate, then throw a RangeError"); + +test(() => { + smokeTest(); + const JSONSerializables = [[], { object: {} }]; + for (const data of JSONSerializables) { + try { + const methods = [ + { + supportedMethods: "https://{{domains[nonexistent]}}/payment-request", + data, + }, + ]; + new PaymentRequest(methods, defaultDetails); + } catch (err) { + assert_unreached( + `Unexpected error parsing stringifiable JSON: ${JSON.stringify( + data + )}: ${err.message}` + ); + } + } +}, "Modifier method data must be JSON-serializable object"); + +test(() => { + smokeTest(); + const recursiveDictionary = {}; + recursiveDictionary.foo = recursiveDictionary; + assert_throws_js(TypeError, () => { + const methods = [ + { + supportedMethods: "https://{{domains[nonexistent]}}/payment-request", + data: recursiveDictionary, + }, + ]; + new PaymentRequest(methods, defaultDetails); + }); + assert_throws_js(TypeError, () => { + const methods = [ + { + supportedMethods: "https://{{domains[nonexistent]}}/payment-request", + data: "a string", + }, + ]; + new PaymentRequest(methods, defaultDetails); + }); + assert_throws_js( + TypeError, + () => { + const methods = [ + { + supportedMethods: "https://{{domains[nonexistent]}}/payment-request", + data: null, + }, + ]; + new PaymentRequest(methods, defaultDetails); + }, + "Even though null is JSON-serializable, it's not type 'Object' per ES spec" + ); +}, "Rethrow any exceptions of JSON-serializing paymentMethod.data into a string"); + +// process total +const invalidAmounts = [ + "-", + "notdigits", + "ALSONOTDIGITS", + "10.", + ".99", + "-10.", + "-.99", + "10-", + "1-0", + "1.0.0", + "1/3", + "", + null, + " 1.0 ", + " 1.0 ", + "1.0 ", + "USD$1.0", + "$1.0", + { + toString() { + return " 1.0"; + }, + }, +]; +const invalidTotalAmounts = invalidAmounts.concat([ + "-1", + "-1.0", + "-1.00", + "-1000.000", + -10, +]); +test(() => { + smokeTest(); + for (const invalidAmount of invalidTotalAmounts) { + const invalidDetails = { + total: { + label: "", + amount: { + currency: "USD", + value: invalidAmount, + }, + }, + }; + assert_throws_js( + TypeError, + () => { + new PaymentRequest(defaultMethods, invalidDetails); + }, + `Expect TypeError when details.total.amount.value is ${invalidAmount}` + ); + } +}, `If details.total.amount.value is not a valid decimal monetary value, then throw a TypeError`); + +test(() => { + smokeTest(); + for (const prop in ["displayItems", "modifiers"]) { + try { + const details = Object.assign({}, defaultDetails, { [prop]: [] }); + new PaymentRequest(defaultMethods, details); + assert_unreached(`PaymentDetailsBase.${prop} can be zero length`); + } catch (err) {} + } +}, `PaymentDetailsBase members can be 0 length`); + +test(() => { + smokeTest(); + assert_throws_js(TypeError, () => { + new PaymentRequest(defaultMethods, { + total: { + label: "", + amount: { + currency: "USD", + value: "-1.00", + }, + }, + }); + }); +}, "If the first character of details.total.amount.value is U+002D HYPHEN-MINUS, then throw a TypeError"); + +test(() => { + smokeTest(); + for (const invalidAmount of invalidAmounts) { + const invalidDetails = { + total: defaultAmount, + displayItems: [ + { + label: "", + amount: { + currency: "USD", + value: invalidAmount, + }, + }, + ], + }; + assert_throws_js( + TypeError, + () => { + new PaymentRequest(defaultMethods, invalidDetails); + }, + `Expected TypeError when item.amount.value is "${invalidAmount}"` + ); + } +}, `For each item in details.displayItems: if item.amount.value is not a valid decimal monetary value, then throw a TypeError`); + +test(() => { + smokeTest(); + try { + new PaymentRequest( + [ + { + supportedMethods: "https://{{domains[nonexistent]}}/payment-request", + }, + ], + { + total: defaultTotal, + displayItems: [ + { + label: "", + amount: { + currency: "USD", + value: "-1000", + }, + }, + { + label: "", + amount: { + currency: "AUD", + value: "-2000.00", + }, + }, + ], + } + ); + } catch (err) { + assert_unreached( + `shouldn't throw when given a negative value: ${err.message}` + ); + } +}, "Negative values are allowed for displayItems.amount.value, irrespective of total amount"); + +test(() => { + smokeTest(); + const largeMoney = "1".repeat(510); + try { + new PaymentRequest(defaultMethods, { + total: { + label: "", + amount: { + currency: "USD", + value: `${largeMoney}.${largeMoney}`, + }, + }, + displayItems: [ + { + label: "", + amount: { + currency: "USD", + value: `-${largeMoney}`, + }, + }, + { + label: "", + amount: { + currency: "AUD", + value: `-${largeMoney}.${largeMoney}`, + }, + }, + ], + }); + } catch (err) { + assert_unreached( + `shouldn't throw when given absurd monetary values: ${err.message}` + ); + } +}, "it handles high precision currency values without throwing"); + +// Process payment details modifiers: +test(() => { + smokeTest(); + for (const invalidTotal of invalidTotalAmounts) { + const invalidModifier = { + supportedMethods: "https://{{domains[nonexistent]}}/payment-request", + total: { + label: "", + amount: { + currency: "USD", + value: invalidTotal, + }, + }, + }; + assert_throws_js( + TypeError, + () => { + new PaymentRequest(defaultMethods, { + modifiers: [invalidModifier], + total: defaultTotal, + }); + }, + `Expected TypeError for modifier.total.amount.value: "${invalidTotal}"` + ); + } +}, `Throw TypeError if modifier.total.amount.value is not a valid decimal monetary value`); + +test(() => { + smokeTest(); + for (const invalidAmount of invalidAmounts) { + const invalidModifier = { + supportedMethods: "https://{{domains[nonexistent]}}/payment-request", + total: defaultTotal, + additionalDisplayItems: [ + { + label: "", + amount: { + currency: "USD", + value: invalidAmount, + }, + }, + ], + }; + assert_throws_js( + TypeError, + () => { + new PaymentRequest(defaultMethods, { + modifiers: [invalidModifier], + total: defaultTotal, + }); + }, + `Expected TypeError when given bogus modifier.additionalDisplayItems.amount of "${invalidModifier}"` + ); + } +}, `If amount.value of additionalDisplayItems is not a valid decimal monetary value, then throw a TypeError`); + +test(() => { + smokeTest(); + const modifiedDetails = Object.assign({}, defaultDetails, { + modifiers: [ + { + supportedMethods: "https://{{domains[nonexistent]}}/payment-request", + data: ["some-data"], + }, + ], + }); + try { + new PaymentRequest(defaultMethods, modifiedDetails); + } catch (err) { + assert_unreached( + `Unexpected exception thrown when given a list: ${err.message}` + ); + } +}, "Modifier data must be JSON-serializable object (an Array in this case)"); + +test(() => { + smokeTest(); + const modifiedDetails = Object.assign({}, defaultDetails, { + modifiers: [ + { + supportedMethods: "https://{{domains[nonexistent]}}/payment-request", + data: { + some: "data", + }, + }, + ], + }); + try { + new PaymentRequest(defaultMethods, modifiedDetails); + } catch (err) { + assert_unreached( + `shouldn't throw when given an object value: ${err.message}` + ); + } +}, "Modifier data must be JSON-serializable object (an Object in this case)"); + +test(() => { + smokeTest(); + const recursiveDictionary = {}; + recursiveDictionary.foo = recursiveDictionary; + const modifiedDetails = Object.assign({}, defaultDetails, { + modifiers: [ + { + supportedMethods: "https://{{domains[nonexistent]}}/payment-request", + data: recursiveDictionary, + }, + ], + }); + assert_throws_js(TypeError, () => { + new PaymentRequest(defaultMethods, modifiedDetails); + }); +}, "Rethrow any exceptions of JSON-serializing modifier.data"); + +</script> diff --git a/testing/web-platform/tests/payment-request/payment-request-ctor-currency-code-checks.https.sub.html b/testing/web-platform/tests/payment-request/payment-request-ctor-currency-code-checks.https.sub.html new file mode 100644 index 0000000000..c608608c7e --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-ctor-currency-code-checks.https.sub.html @@ -0,0 +1,282 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test currency code usage in PaymentRequest Constructor</title> +<link rel="help" href="https://w3c.github.io/browser-payment-api/#constructor"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +const defaultMethods = [ + Object.freeze({ + supportedMethods: "https://{{domains[nonexistent]}}", + }), +]; +const defaultAmount = Object.freeze({ + currency: "USD", + value: "1.00", +}); +const defaultTotal = Object.freeze({ + label: "Total", + amount: defaultAmount, +}); + +const defaultDetails = Object.freeze({ + total: Object.freeze({ + label: "", + amount: defaultAmount, + }), +}); + +// The following are the same set of valid/invalid codes that are used in +// the ECMAScript Internationalization API Specification (ECMA-402) test suite. +const wellFormedCurrencyCodes = [ + "BOB", + "EUR", + "usd", // currency codes are case-insensitive + "XdR", + "xTs", +]; + +const invalidCurrencyCodes = [ + "", + "€", + "$", + "SFr.", + "DM", + "KR₩", + "702", + "ßP", + "ınr", +]; + +const RANGE_ERROR = RangeError; + +const invalidAmount = Object.freeze({ + currency: "¡INVALID!", + value: "1.00", +}); + +const invalidTotal = { + total: { + label: "Invalid total", + amount: invalidAmount, + }, +}; + +// Ensure we don't get false positives +function smokeTest() { + new PaymentRequest(defaultMethods, invalidTotal); +} + +// Process the total: +test(() => { + assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw."); + for (const validCurrency of wellFormedCurrencyCodes) { + const amount = { + currency: validCurrency, + value: "1.00", + }; + const total = { + label: "Total", + amount, + }; + const details = { + total, + }; + try { + new PaymentRequest(defaultMethods, details); + } catch (err) { + assert_unreached( + `Unexpected exception for details.total.amount "${validCurrency}": ${err.message}` + ); + } + } +}, "Check and canonicalize valid details.total.amount"); + +test(() => { + assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw."); + for (const invalidCurrency of invalidCurrencyCodes) { + const amount = { + currency: invalidCurrency, + value: "1.00", + }; + const total = { + label: "Total", + amount, + }; + const details = { + total, + }; + assert_throws_js( + RANGE_ERROR, + () => { + new PaymentRequest(defaultMethods, details); + }, + `Expected RangeError for details.total.amount given ("${invalidCurrency}").` + ); + } +}, "Check and canonicalize invalid details.total.amount and rethrow any exceptions."); + +// If the displayItems member of details is present, then for each item in details.displayItems: +test(() => { + assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw."); + const displayItems = []; + for (const validCurrency of wellFormedCurrencyCodes) { + const amount = { + currency: validCurrency, + value: "123", + }; + const displayItem = { + amount, + label: "valid currency", + }; + const details = { + total: defaultTotal, + displayItems: [displayItem], + }; + try { + new PaymentRequest(defaultMethods, details); + } catch (err) { + assert_unreached( + `Unexpected error with displayItem that had a valid currency "${validCurrency}": ${err.message}` + ); + } + displayItems.push(displayItem); + } + // Let's make sure it doesn't throw given a list of valid displayItems + try { + const details = Object.assign({}, defaultDetails, { displayItems }); + new PaymentRequest(defaultMethods, details); + } catch (err) { + assert_unreached( + `Unexpected error with multiple valid displayItems: ${err.message}` + ); + } +}, "Check and canonicalize valid details.displayItems amount"); + +test(() => { + assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw."); + for (const invalidCurrency of invalidCurrencyCodes) { + const amount = { + currency: invalidCurrency, + value: "123", + }; + const displayItem = { + amount, + label: "invalid currency", + }; + const details = { + total: defaultTotal, + displayItems: [displayItem], + }; + assert_throws_js( + RANGE_ERROR, + () => { + new PaymentRequest(defaultMethods, details); + }, + `Expected RangeError with invalid displayItem currency "${invalidCurrency}".` + ); + } +}, "Check and canonicalize invalid details.displayItems amount and rethrow RangeError."); + +// Process payment details modifiers: +test(() => { + assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw."); + for (const validCurrency of wellFormedCurrencyCodes) { + const modifier = { + supportedMethods: "https://{{domains[nonexistent]}}", + total: { + label: "Total due", + amount: { currency: validCurrency, value: "68.00" }, + }, + }; + const details = { + total: defaultTotal, + modifiers: [modifier], + }; + try { + new PaymentRequest(defaultMethods, details); + } catch (err) { + assert_unreached( + `Unexpected error with valid modifier currency code "${validCurrency}": ${err.message}` + ); + } + } +}, "Check and canonicalize valid modifiers[n].total amount."); + +test(() => { + assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw."); + for (const invalidCurrency of invalidCurrencyCodes) { + const modifier = { + supportedMethods: "https://{{domains[nonexistent]}}", + total: { + label: "Total due", + amount: { currency: invalidCurrency, value: "68.00" }, + }, + }; + const details = { + total: defaultTotal, + modifiers: [modifier], + }; + assert_throws_js( + RANGE_ERROR, + () => { + new PaymentRequest(defaultMethods, details); + }, + `Expected RangeError with invalid modifier currency code "${invalidCurrency}".` + ); + } +}, "Check and canonicalize invalid modifiers[n].total amount and rethrow RangeError."); + +// Process payment details modifiers: +test(() => { + assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw."); + for (const validCurrency of wellFormedCurrencyCodes) { + const additionalItem = { + label: "additionalItem", + amount: { currency: validCurrency, value: "3.00" }, + }; + const modifier = { + supportedMethods: "https://{{domains[nonexistent]}}", + total: defaultTotal, + additionalDisplayItems: [additionalItem], + }; + const details = { + total: defaultTotal, + modifiers: [modifier], + }; + try { + new PaymentRequest(defaultMethods, details); + } catch (err) { + assert_unreached( + `Unexpected error with valid additionalDisplayItems[n] currency code "${validCurrency}": ${err.message}` + ); + } + } +}, "Check and canonicalize valid modifiers[n].additionaDisplayItem amount."); + +test(() => { + assert_throws_js(RANGE_ERROR, smokeTest, "Expected smoke test to throw."); + for (const invalidCurrency of invalidCurrencyCodes) { + const additionalItem = { + label: "additionalItem", + amount: { currency: invalidCurrency, value: "3.00" }, + }; + const modifier = { + supportedMethods: "https://{{domains[nonexistent]}}", + total: defaultTotal, + additionalDisplayItems: [additionalItem], + }; + const details = { + total: defaultTotal, + modifiers: [modifier], + }; + assert_throws_js( + RANGE_ERROR, + () => { + new PaymentRequest(defaultMethods, details); + }, + `Expected RangeError with invalid additionalDisplayItems[n] currency code "${invalidCurrency}".` + ); + } +}, "Check and canonicalize invalid modifiers[n].additionaDisplayItem amount and rethrow RangeError."); +</script> diff --git a/testing/web-platform/tests/payment-request/payment-request-ctor-pmi-handling.https.sub.html b/testing/web-platform/tests/payment-request/payment-request-ctor-pmi-handling.https.sub.html new file mode 100644 index 0000000000..d6a1be2394 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-ctor-pmi-handling.https.sub.html @@ -0,0 +1,149 @@ +<!DOCTYPE html> +<!-- Copyright © 2017 Mozilla and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). --> +<meta charset="utf-8"> +<title>Test for validity of payment method identifiers during construction</title> +<link rel="help" href="https://w3c.github.io/browser-payment-api/#constructor"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +"use strict"; +const validAmount = Object.freeze({ + currency: "USD", + value: "1.0", +}); +const validTotal = Object.freeze({ + label: "Default Total", + amount: validAmount, +}); +const defaultDetails = Object.freeze({ + total: validTotal, +}); + +test(() => { + const validMethods = [ + "https://wpt", + "https://{{domains[nonexistent]}}/", + "https://{{domains[nonexistent]}}/payment", + "https://{{domains[nonexistent]}}/payment-request", + "https://{{domains[nonexistent]}}/payment-request?", + "https://{{domains[nonexistent]}}/payment-request?this=is", + "https://{{domains[nonexistent]}}/payment-request?this=is&totally", + "https://{{domains[nonexistent]}}:443/payment-request?this=is&totally", + "https://{{domains[nonexistent]}}:443/payment-request?this=is&totally#fine", + "https://:@{{domains[nonexistent]}}:443/payment-request?this=is&totally#👍", + " \thttps://wpt\n ", + "https://xn--c1yn36f", + "https://點看", + ]; + for (const validMethod of validMethods) { + try { + const methods = [{ supportedMethods: validMethod }]; + new PaymentRequest(methods, defaultDetails); + } catch (err) { + assert_unreached( + `Unexpected exception with valid standardized PMI: ${validMethod}. ${err}` + ); + } + } +}, "Must support valid standard URL PMIs"); + +test(() => { + const validMethods = [ + "e", + "n6jzof05mk2g4lhxr-u-q-w1-c-i-pa-ty-bdvs9-ho-ae7-p-md8-s-wq3-h-qd-e-q-sa", + "a-b-q-n-s-pw0", + "m-u", + "s-l5", + "k9-f", + "m-l", + "u4-n-t", + "i488jh6-g18-fck-yb-v7-i", + "x-x-t-t-c34-o", + "secure-payment-confirmation", + // gets coerced to "secure-payment-confirmation", for compat with old version of spec + ["secure-payment-confirmation"], + ]; + for (const validMethod of validMethods) { + try { + const methods = [{ supportedMethods: validMethod }]; + new PaymentRequest(methods, defaultDetails); + } catch (err) { + assert_unreached( + `Unexpected exception with valid standardized PMI: ${validMethod}. ${err}` + ); + } + } +}, "Must not throw on syntactically valid standardized payment method identifiers, even if they are not supported"); + +test(() => { + const invalidMethods = [ + "secure-💳", + "¡secure-*-payment-confirmation!", + "Secure-Payment-Confirmation", + "0", + "-", + "--", + "a--b", + "-a--b", + "a-b-", + "0-", + "0-a", + "a0--", + "A-", + "A-B", + "A-b", + "a-0", + "a-0b", + " a-b", + "\t\na-b", + "a-b ", + "a-b\n\t", + "secure-payment-confirmation?not-really", + "secure-payment-confirmation://not-ok", + "secure payment confirmation", + "/secure payment confirmation/", + "SeCuRePaYmEnTcOnFiRmAtIoN", + "SECURE-PAYMENT-CONFIRMATION", + " secure-payment-confirmation ", + "this is not supported", + " ", + "foo,var", + ["visa","mastercard"], // stringifies to "visa,mastercard" + ]; + for (const invalidMethod of invalidMethods) { + assert_throws_js( + RangeError, + () => { + const methods = [{ supportedMethods: invalidMethod }]; + new PaymentRequest(methods, defaultDetails); + }, + `expected RangeError processing invalid standardized PMI "${invalidMethod}"` + ); + } +}, "Must throw on syntactically invalid standardized payment method identifiers"); + +test(() => { + const invalidMethods = [ + "https://username@example.com/pay", + "https://:password@example.com/pay", + "https://username:password@example.com/pay", + "http://username:password@example.com/pay", + "http://foo.com:100000000/pay", + "not-https://{{domains[nonexistent]}}/payment-request", + "../realitive/url", + "/absolute/../path?", + "https://", + ]; + for (const invalidMethod of invalidMethods) { + assert_throws_js( + RangeError, + () => { + const methods = [{ supportedMethods: invalidMethod }]; + new PaymentRequest(methods, defaultDetails); + }, + `expected RangeError processing invalid URL PMI "${invalidMethod}"` + ); + } +}, "Constructor MUST throw if given an invalid URL-based payment method identifier"); + +</script> diff --git a/testing/web-platform/tests/payment-request/payment-request-disallowed-when-hidden.https.html b/testing/web-platform/tests/payment-request/payment-request-disallowed-when-hidden.https.html new file mode 100644 index 0000000000..3a5eb01015 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-disallowed-when-hidden.https.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test for PaymentRequest.show() method - should fail when tab is not visible</title> +<link rel="help" href="https://w3c.github.io/payment-request/#show-method"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<!-- For minimize() --> +<script src="/page-visibility/resources/window_state_context.js"></script> +<script> +'use strict'; + +promise_test(async t => { + const {minimize, restore} = window_state_context(t); + + const request = new PaymentRequest([ + { + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + }, + }, + ], { + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + } + }); + + // `bless` simulates a click so it must happen before minimizing the window. + await test_driver.bless('user activation'); + + // Before we trigger the Payment Request, minimize the window. This should + // cause the show() call to be rejected. + await minimize(); + assert_equals(document.hidden, true); + + return promise_rejects_dom(t, "AbortError", request.show()); +}, 'PaymentRequest.show() cannot be triggered from a hidden context'); +</script> diff --git a/testing/web-platform/tests/payment-request/payment-request-hasenrolledinstrument-method-manual.tentative.https.html b/testing/web-platform/tests/payment-request/payment-request-hasenrolledinstrument-method-manual.tentative.https.html new file mode 100644 index 0000000000..e6b164f7cc --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-hasenrolledinstrument-method-manual.tentative.https.html @@ -0,0 +1,95 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Manual tests for PaymentRequest.hasEnrolledInstrument() method</title> +<link rel="help" href="https://w3c.github.io/payment-request/#hasenrolledinstrument-method"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +setup({ + explicit_done: true, + explicit_timeout: true, +}); + +const defaultMethods = Object.freeze([ + { + supportedMethods: "basic-card", + data: { + supportedNetworks: [ 'visa' ], + }, + } +]); +const defaultDetails = Object.freeze({ + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, +}); + +function testHasNoEnrolledInstrument() { + promise_test(async t => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + assert_false( + await request.hasEnrolledInstrument(), + "No test enrolled in the test profile." + ); + }, `hasEnrolledInstrument() resolves to false when user has no enrolled instrument.`); +} + +function testHasEnrolledInstrument() { + promise_test(async t => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + assert_true( + await request.hasEnrolledInstrument(), + "A card is enrolled in the test profile." + ); + }, `hasEnrolledInstrument() resolves to true when user has an enrolled instrument.`); +} + +function testHasEnrolledInstrumentAgain() { + promise_test(async t => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + assert_true( + await request.hasEnrolledInstrument(), + "A card is enrolled in the test profile." + ); + }, `hasEnrolledInstrument() can be called multiple times if the payment method details are identical.`); +} +</script> + +<h2>Manual tests for hasEnrolledInstrument() method</h2> +<p> + Follow the instructions from top to bottom. Click on each button in sequence + without refreshing the page. Some of the tests will bring up the Payment + Request UI and close them automatically. If a payment sheet stays open, the + test has failed. +</p> +<ol> + <li>Follow browser-specific instructions to remove all cards from the test profile.</li> + <li> + <button onclick="testHasNoEnrolledInstrument()"> + hasEnrolledInstrument() resolves to false when user has no enrolled instrument. + </button> + </li> + <li>Add a test Visa card to your test profile, e.g. 4012888888881881.</li> + <li> + <button onclick="testHasEnrolledInstrument()"> + hasEnrolledInstrument() resolves to true when user has an enrolled instrument. + </button> + </li> + <li> + <button onclick="testHasEnrolledInstrumentAgain()"> + hasEnrolledInstrument() can be called multiple times if the payment method + details are identical. + </button> + </li> + <li> + <button onclick="done()">Done!</button> + </li> +</ol> +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-request-hasenrolledinstrument-method-protection.tentative.https.html b/testing/web-platform/tests/payment-request/payment-request-hasenrolledinstrument-method-protection.tentative.https.html new file mode 100644 index 0000000000..4da11304a2 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-hasenrolledinstrument-method-protection.tentative.https.html @@ -0,0 +1,68 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Tests for PaymentRequest.hasEnrolledInstrument() method</title> +<link rel="help" href="https://w3c.github.io/payment-request/#hasenrolledinstrument-method"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src='/resources/testdriver-vendor.js'></script> +<script> +const visaMethod = Object.freeze({ + supportedMethods: "basic-card", + data: { + supportedNetworks: ['visa'] + } +}); +const mastercardMethod = Object.freeze({ + supportedMethods: "basic-card", + data: { + supportedNetworks: ['mastercard'] + } +}); +const defaultDetails = Object.freeze({ + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, +}); + +promise_test(async t => { + // This test may never actually hit its assertion, but that's allowed. + const request = new PaymentRequest([visaMethod], defaultDetails); + for (let i = 0; i < 1000; i++) { + try { + await request.hasEnrolledInstrument(); + } catch (err) { + assert_equals( + err.name, + "NotAllowedError", + "If it throws, then it must be a NotAllowedError." + ); + break; + } + } + + for (let i = 0; i < 1000; i++) { + try { + const request2 = new PaymentRequest([mastercardMethod], defaultDetails); + await request2.hasEnrolledInstrument(); + } catch (err) { + assert_equals( + err.name, + "NotAllowedError", + "If it throws, then it must be a NotAllowedError." + ); + break; + } + } +}, `Optionally, at the user agent's discretion, return a promise rejected with a "NotAllowedError" DOMException.`); + +</script> + +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-request-hasenrolledinstrument-method.tentative.https.html b/testing/web-platform/tests/payment-request/payment-request-hasenrolledinstrument-method.tentative.https.html new file mode 100644 index 0000000000..4f6b7e9239 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-hasenrolledinstrument-method.tentative.https.html @@ -0,0 +1,78 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Tests for PaymentRequest.hasEnrolledInstrument() method</title> +<link rel="help" href="https://w3c.github.io/payment-request/#hasenrolledinstrument-method"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src='/resources/testdriver-vendor.js'></script> +<script> +"use strict"; + +setup({ allow_uncaught_exception: true }); + +const unsupportedMethods = [ + { supportedMethods: "this-is-not-supported" }, + { supportedMethods: "https://not.supported" }, +]; +const defaultMethods = Object.freeze([ + { + supportedMethods: "basic-card", + data: { + supportedNetworks: [ 'visa' ], + }, + } +]); +const defaultDetails = Object.freeze({ + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, +}); + +promise_test(async t => { + const request = new PaymentRequest(unsupportedMethods, defaultDetails); + assert_false( + await request.hasEnrolledInstrument(), + "hasEnrolledInstrument() should resolve to false." + ); +}, `hasEnrolledInstrument() resolves to false for unsupported payment methods.`); + +promise_test(async t => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const [acceptPromise, hasEnrolledInstrumentPromise] = await test_driver.bless( + "show payment request", + () => { + const acceptPromise = request.show(); // Sets state to "interactive" + const hasEnrolledInstrumentPromise = request.hasEnrolledInstrument(); + return [acceptPromise, hasEnrolledInstrumentPromise]; + }); + await promise_rejects_dom(t, "InvalidStateError", hasEnrolledInstrumentPromise); + + await request.abort(); + await promise_rejects_dom(t, "AbortError", acceptPromise); +}, `If request.[[state]] is "interactive", then return a promise rejected with an "InvalidStateError" DOMException.`); + +promise_test(async t => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + const [abortPromise, acceptPromise] = await test_driver.bless( "show payment request", () => { + const acceptPromise = request.show(); // Sets state to "interactive" + acceptPromise.catch(() => {}); // no-op, just to handle unhandled rejection in devtools. + const abortPromise =request.abort(); // Sets state to "closed" + return [abortPromise, acceptPromise]; + }); + await abortPromise; + await promise_rejects_dom(t, "AbortError", acceptPromise); + + const hasEnrolledInstrumentPromise = request.hasEnrolledInstrument(); + await promise_rejects_dom(t, "InvalidStateError", hasEnrolledInstrumentPromise); +}, `If request.[[state]] is "closed", then return a promise rejected with an "InvalidStateError" DOMException.`); +</script> + +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-request-id-attribute.https.html b/testing/web-platform/tests/payment-request/payment-request-id-attribute.https.html new file mode 100644 index 0000000000..e5d0c7a66e --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-id-attribute.https.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). --> +<meta charset="utf-8"> +<title>Test for PaymentRequest id attribute</title> +<link rel="help" href="https://w3c.github.io/payment-request/#constructor"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +const methods = [{ supportedMethods: "foo" }]; +const total = { label: "label", amount: { currency: "USD", value: "5.00" } }; + +test(() => { + const request1 = new PaymentRequest(methods, { + id: "pass", + total, + }); + assert_idl_attribute(request1, "id"); + assert_equals(request1.id, "pass", "Expected PaymentRequest.id to be 'pass'"); +}, "PaymentRequest's id attribute's value can be set via PaymentDetailsInit dictionary"); + +// Test for https://github.com/w3c/payment-request/pull/665 +test(() => { + const uuidRegExp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + const request1 = new PaymentRequest(methods, { + total, + }); + const request2 = new PaymentRequest(methods, { + total, + }); + assert_true( + uuidRegExp.test(request1.id) && uuidRegExp.test(request2.id) , + "Expected PaymentRequest.id be a UUID" + ); + assert_not_equals( + request1.id, request2.id, + "Expected PaymentRequest.id be unique per instance" + ); +}, "PaymentRequest's id attribute must be a UUID when PaymentDetailsInit.id is missing"); +</script> diff --git a/testing/web-platform/tests/payment-request/payment-request-insecure.http.html b/testing/web-platform/tests/payment-request/payment-request-insecure.http.html new file mode 100644 index 0000000000..02122203d5 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-insecure.http.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<!-- Copyright © 2017 Chromium authors and World Wide Web Consortium, (Massachusetts Institute of Technology, ERCIM, Keio University, Beihang). --> +<meta charset="utf-8"> +<title>Test for PaymentRequest Constructor (insecure)</title> +<link rel="help" href="https://w3c.github.io/payment-request/#paymentrequest-interface"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +test(() => { + assert_false(isSecureContext); + assert_false("PaymentRequest" in window); +}, "PaymentRequest constructor must not be exposed in insecure context"); +</script> diff --git a/testing/web-platform/tests/payment-request/payment-request-not-exposed.https.worker.js b/testing/web-platform/tests/payment-request/payment-request-not-exposed.https.worker.js new file mode 100644 index 0000000000..e5576e6735 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-not-exposed.https.worker.js @@ -0,0 +1,7 @@ +importScripts("/resources/testharness.js"); + +test(() => { + assert_true(isSecureContext); + assert_false('PaymentRequest' in self); +}, "PaymentRequest constructor must not be exposed in worker global scope"); +done(); diff --git a/testing/web-platform/tests/payment-request/payment-request-show-method.https.html b/testing/web-platform/tests/payment-request/payment-request-show-method.https.html new file mode 100644 index 0000000000..d3385b5468 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-request-show-method.https.html @@ -0,0 +1,105 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>Test for PaymentRequest.show() method</title> +<link rel="help" href="https://w3c.github.io/payment-request/#show-method" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script> +"use strict"; + +setup({ allow_uncaught_exception: true }); + +const defaultMethods = Object.freeze([ + { supportedMethods: "basic-card" }, + { + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + }, + }, +]); + +const defaultDetails = Object.freeze({ + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, +}); + +promise_test(async (t) => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + await promise_rejects_dom(t, "SecurityError", request.show()); + + await test_driver.bless(); + // Sets state to "interactive", but consumes user interaction. + const acceptPromise = request.show(); + await promise_rejects_dom(t, "SecurityError", request.show()); + + // With user activation, but already showing the sheet... + await test_driver.bless(); + await promise_rejects_dom(t, "InvalidStateError", request.show()); + + await request.abort(); + await promise_rejects_dom(t, "AbortError", acceptPromise); +}, "Throws if the promise [[state]] is not 'created'."); + +promise_test(async (t) => { + const request1 = new PaymentRequest(defaultMethods, defaultDetails); + await test_driver.bless(); + const acceptPromise1 = request1.show(); + + // Payment request already showing, so... + const request2 = new PaymentRequest(defaultMethods, defaultDetails); + await test_driver.bless(); + await promise_rejects_dom(t, "AbortError", request2.show()); + + await request1.abort(); + await promise_rejects_dom(t, "AbortError", acceptPromise1); +}, `If the user agent's "payment request is showing" boolean is true, then return a promise rejected with an "AbortError" DOMException.`); + +promise_test(async (t) => { + const request = new PaymentRequest( + [{ supportedMethods: "this-is-not-supported" }], + defaultDetails + ); + await test_driver.bless(); + const acceptPromise = request.show(); + await promise_rejects_dom(t, "NotSupportedError", acceptPromise); +}, `If payment method consultation produces no supported method of payment, then return a promise rejected with a "NotSupportedError" DOMException.`); + +promise_test(async (t) => { + const request = new PaymentRequest(defaultMethods, defaultDetails); + await test_driver.bless(); + const p1 = request.show(); + await test_driver.bless(); + const p2 = request.show(); + await test_driver.bless(); + const p3 = request.show(); + await request.abort(); + + const promises = new Set([p1, p2, p3]); + assert_equals(promises.size, 3, "Must have three unique objects"); + + await promise_rejects_dom(t, "AbortError", p1); + await promise_rejects_dom(t, "InvalidStateError", p2); + await promise_rejects_dom(t, "InvalidStateError", p3); +}, "Calling show() multiple times always returns a new promise."); +</script> +<small> + If you find a buggy test, please + <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> and + tag one of the + <a + href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml" + >suggested reviewers</a + >. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-response/complete-method-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/complete-method-manual.https.html new file mode 100644 index 0000000000..f7facd7980 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/complete-method-manual.https.html @@ -0,0 +1,101 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-complete()"> +<title> + PaymentResponse.prototype.complete() method +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="helpers.js"></script> +<script> +async function runManualTest({ completeWith: result }, button) { + button.disabled = true; + const { response, request } = await getPaymentRequestResponse(); + promise_test(async t => { + let completePromise; + let invalidComplete; + let afterComplete; + try { + // We .complete() as normal, using the passed test value + completePromise = response.complete(result); + assert_true(completePromise instanceof Promise, "returns a promise"); + // Immediately calling complete() again yields a rejected promise. + invalidComplete = response.complete(result); + await promise_rejects_dom(t, "InvalidStateError", invalidComplete); + // but the original promise is unaffected + const returnedValue = await completePromise; + assert_equals( + returnedValue, + undefined, + "Returned value must always be undefined" + ); + // We now call .complete() again, to force an exception + // because [[complete]] is true. + afterComplete = response.complete(result); + await promise_rejects_dom(t, "InvalidStateError", afterComplete); + button.innerHTML = `✅ ${button.textContent}`; + } catch (err) { + button.innerHTML = `❌ ${button.textContent}`; + assert_unreached("Unexpected exception: " + err.message); + } + const allPromises = new Set([ + completePromise, + invalidComplete, + afterComplete, + ]); + assert_equals( + allPromises.size, + 3, + "Calling complete() multiple times is always a new object." + ); + }, button.textContent.trim()); +} +</script> + +<h2> + Manual Tests for PaymentResponse.complete() - Please run in order! +</h2> +<p> + Click on each button in sequence from top to bottom without refreshing the page. + Each button will bring up the Payment Request UI window. +</p> +<p> + When presented with the payment sheet, use any credit card select to "Pay". + Also confirm any prompts that come up. +</p> +<ol> + <li> + <button onclick="runManualTest({completeWith: 'success'}, this)"> + If the value of the internal slot [[completeCalled]] is true, + reject promise with an "InvalidStateError" DOMException. + </button> + </li> + <li> + <button onclick="runManualTest({completeWith: undefined}, this)"> + Passing no argument defaults to "unknown", + eventually closing the sheet and doesn't throw. + </button> + </li> + <li> + <button onclick="runManualTest({completeWith: 'success'}, this)"> + Passing "success" eventually closes the sheet and doesn't throw. + </button> + </li> + <li> + <button onclick="runManualTest({completeWith: 'fail'}, this)"> + Passing "fail" eventually closes the sheet and doesn't throw. + </button> + </li> + <li> + <button onclick="runManualTest({completeWith: 'unknown'}, this)"> + Passing "unknown" eventually closes the sheet and doesn't throw. + </button> + </li> + <li> + <button onclick="done()">Done!</button> + </li> +</ol> +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-response/helpers.js b/testing/web-platform/tests/payment-request/payment-response/helpers.js new file mode 100644 index 0000000000..1242ecb743 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/helpers.js @@ -0,0 +1,110 @@ +setup({ explicit_done: true, explicit_timeout: true }); + +const applePay = Object.freeze({ + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + } +}); + +const validMethod = Object.freeze({ + supportedMethods: "basic-card", +}); + +const validMethods = Object.freeze([validMethod, applePay]); + +const validAmount = Object.freeze({ + currency: "USD", + value: "1.00", +}); + +const validTotal = Object.freeze({ + label: "Valid total", + amount: validAmount, +}); +const validDetails = { + total: validTotal, +}; + +test(() => { + try { + new PaymentRequest(validMethods, validDetails); + } catch (err) { + done(); + throw err; + } +}, "Can construct a payment request (smoke test)."); + +/** + * Pops up a payment sheet, allowing options to be + * passed in if particular values are needed. + * + * @param PaymentOptions options + */ +async function getPaymentResponse(options, id) { + const { response } = await getPaymentRequestResponse(options, id); + return response; +} + +/** + * Creates a payment request and response pair. + * It also shows the payment sheet. + * + * @param {PaymentOptions?} options + * @param {String?} id + */ +async function getPaymentRequestResponse(options, id) { + const methods = [{ supportedMethods: "basic-card" }]; + const details = { + id, + total: { + label: "Total due", + amount: { currency: "USD", value: "1.0" }, + }, + }; + const request = new PaymentRequest(methods, details, options); + const response = await request.show(); + return { request, response }; +} + +/** + * Runs a manual test for payment + * + * @param {HTMLButtonElement} button The HTML button. + * @param {PaymentOptions?} options. + * @param {Object} expected What property values are expected to pass the test. + * @param {String?} id And id for the request/response pair. + */ +async function runManualTest(button, options, expected = {}, id = undefined) { + button.disabled = true; + const { request, response } = await getPaymentRequestResponse(options, id); + await response.complete(); + test(() => { + assert_idl_attribute( + response, + "requestId", + "Expected requestId to be an IDL attribute." + ); + assert_equals(response.requestId, request.id, `Expected ids to match`); + for (const [attribute, value] of Object.entries(expected)) { + assert_idl_attribute( + response, + attribute, + `Expected ${attribute} to be an IDL attribute.` + ); + assert_equals( + response[attribute], + value, + `Expected response ${attribute} attribute to be ${value}` + ); + } + assert_idl_attribute(response, "details"); + assert_equals(typeof response.details, "object", "Expected an object"); + // Testing that this does not throw: + response.toJSON(); + }, button.textContent.trim()); +} diff --git a/testing/web-platform/tests/payment-request/payment-response/methodName-attribute-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/methodName-attribute-manual.https.html new file mode 100644 index 0000000000..0a8ef6c77e --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/methodName-attribute-manual.https.html @@ -0,0 +1,28 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-methodname"> +<title> + PaymentResponse.prototype.methodName attribute +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="helpers.js"></script> +<h2>methodName attribute</h2> +<p> + Click on each button in sequence from top to bottom without refreshing the page. + Each button will bring up the Payment Request UI window. +</p> +<p> + Use any credit card and any values. +</p> +<ol> + <li> + <button onclick="runManualTest(this, {}, { methodName: 'basic-card' }).then(done)"> + Expect the payment method identifier to be 'basic-card'. + </button> + </li> +</ol> +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-response/onpayerdetailchange-attribute-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/onpayerdetailchange-attribute-manual.https.html new file mode 100644 index 0000000000..5731952c0e --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/onpayerdetailchange-attribute-manual.https.html @@ -0,0 +1,73 @@ +<!doctype html> +<meta charset=utf-8> +<title>PaymentResponse.prototype.onpayerdetailchange attribute</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src="helpers.js"></script> +<script> +function runTest(button, options, expected){ + button.disabled = true; + promise_test(async () => { + const response = await getPaymentResponse(options); + const eventPromise = new Promise(resolve => { + response.addEventListener("payerdetailchange", resolve); + }); + const error = button.previousElementSibling.textContent.trim(); + await response.retry({ error }); + const event = await eventPromise; + assert_true(event instanceof PaymentRequestUpdateEvent); + for([prop, value] of Object.entries(expected)){ + if (prop === 'payerPhone') { + // |payerPhone| may optionally adhere to E164 structure, which does not + // contain formatting, e.g. +180000000 instead of +1-800-000-0000. + // Strip out the formatting in case the user agent implements E164. + // https://w3c.github.io/payment-request/#addressinit-dictionary + value = value.replace(/[-\(\) ]/g, ''); + } + assert_equals(response[prop], value); + } + await response.complete("success"); + }, button.textContent.trim()); +} +</script> +<h2>Handling PaymentResponse.prototype.onpayerdetailchange events</h2> +<p> + Each button will bring up the Payment Request UI window. + When shown the payment sheet, use any details and hit pay. +</p> +<p> + When asked to retry the payment: +</p> +<ol> + <li> + <p> + Change payer's name to "pass". + </p> + <button onclick="runTest(this, { requestPayerName: true }, { payerName: 'pass' });"> + PaymentRequestUpdateEvent is dispatched when payer name changes. + </button> + </li> + <li> + <p> + Change payer's email to "pass@pass.pass". + </p> + <button onclick="runTest(this, {requestPayerEmail: true}, { payerEmail: 'pass@pass.pass' });"> + PaymentRequestUpdateEvent is dispatched when payer email changes. + </button> + </li> + <li> + <p> + Change payer's phone to "+1-800-000-0000". + </p> + <button onclick="runTest(this, {requestPayerPhone: true}, { payerPhone: '+18000000000' })"> + PaymentRequestUpdateEvent is dispatched when payer phone changes. + </button> + </li> + <li> + <button onclick="done();">DONE!</button> + </li> +</ol> +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">owners</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-response/onpayerdetailchange-attribute.https.html b/testing/web-platform/tests/payment-request/payment-response/onpayerdetailchange-attribute.https.html new file mode 100644 index 0000000000..ed9e6e885b --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/onpayerdetailchange-attribute.https.html @@ -0,0 +1,14 @@ +<!doctype html> +<meta charset=utf-8> +<title>PaymentResponse.prototype.onpayerdetailschange attribute</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script> +test(() => { + assert_equals(Object.getPrototypeOf(PaymentResponse), window.EventTarget); +}, "PaymentResponse inherits from EventTarget"); + +test(() => { + assert_true("onpayerdetailchange" in PaymentResponse.prototype); +}, "PaymentResponse has an onpayerdetailchange in the prototype chain"); +</script> diff --git a/testing/web-platform/tests/payment-request/payment-response/payerEmail-attribute-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/payerEmail-attribute-manual.https.html new file mode 100644 index 0000000000..28ce4c28a8 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/payerEmail-attribute-manual.https.html @@ -0,0 +1,48 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-payeremail"> +<title> + PaymentResponse.prototype.payerEmail attribute +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="helpers.js"></script> +<h2>payerEmail attribute</h2> +<p> + Click on each button in sequence from top to bottom without refreshing the page. + Each button will bring up the Payment Request UI window. +</p> +<p> + When requested, please use "wpt@w3.org" as the email. +</p> +<ol> + <li> + <button onclick="runManualTest(this, undefined, { payerEmail: null })"> + payerEmail attribute is null when options undefined. + </button> + </li> + <li> + <button onclick="runManualTest(this, { requestPayerEmail: undefined }, { payerEmail: null })"> + payerEmail attribute is null when requestPayerEmail is undefined. + </button> + </li> + <li> + <button onclick="runManualTest(this, { requestPayerEmail: false }, { payerEmail: null })"> + payerEmail attribute is null when requestPayerEmail is false. + </button> + </li> + <li> + <button onclick="runManualTest(this, { requestPayerEmail: true }, { payerEmail: 'wpt@w3.org' })"> + payerEmail attribute is 'wpt@w3.org' when requestPayerEmail is true. + </button> + </li> + <li> + <button onclick="runManualTest(this, { requestPayerEmail: 'yep' }, { payerEmail: 'wpt@w3.org' }).then(done)"> + payerEmail attribute is 'wpt@w3.org' when requestPayerEmail is truthy. + </button> + </li> +</ol> +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-response/payerName-attribute-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/payerName-attribute-manual.https.html new file mode 100644 index 0000000000..44d741ae45 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/payerName-attribute-manual.https.html @@ -0,0 +1,48 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-payername"> +<title> + PaymentResponse.prototype.payerName attribute +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="helpers.js"></script> +<h2>payerName attribute</h2> +<p> + Click on each button in sequence from top to bottom without refreshing the page. + Each button will bring up the Payment Request UI window. +</p> +<p> + When requested, please use "web platform test" as the payer name. +</p> +<ol> + <li> + <button onclick="runManualTest(this, undefined, { payerName: null })"> + payerName attribute is null when option is undefined. + </button> + </li> + <li> + <button onclick="runManualTest(this, { requestPayerName: undefined }, { payerName: null })"> + payerName attribute is null when requestPayerName is undefined. + </button> + </li> + <li> + <button onclick="runManualTest(this, { requestPayerName: false }, { payerName: null })"> + payerName attribute is null when requestPayerName is false. + </button> + </li> + <li> + <button onclick="runManualTest(this, { requestPayerName: true }, { payerName: 'web platform test' })"> + payerName attribute is 'web platform test' when requestPayerName is true. + </button> + </li> + <li> + <button onclick="runManualTest(this, { requestPayerName: 'yep' }, { payerName: 'web platform test' }).then(done)"> + payerName attribute is 'web platform test' when requestPayerName is truthy. + </button> + </li> +</ol> +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-response/payerPhone-attribute-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/payerPhone-attribute-manual.https.html new file mode 100644 index 0000000000..85a44a819c --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/payerPhone-attribute-manual.https.html @@ -0,0 +1,48 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-payerphone"> +<title> + PaymentResponse.prototype.payerPhone attribute +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="helpers.js"></script> +<h2>payerPhone attribute</h2> +<p> + Click on each button in sequence from top to bottom without refreshing the page. + Each button will bring up the Payment Request UI window. +</p> +<p> + When prompted, please use +12345678910 as the phone number. +</p> +<ol> + <li> + <button onclick="runManualTest(this, undefined, { payerPhone: null })"> + payerPhone attribute is null when options is undefined. + </button> + </li> + <li> + <button onclick="runManualTest(this, { requestPayerPhone: undefined }, { payerPhone: null })"> + payerPhone attribute is null when requestPayerPhone is undefined. + </button> + </li> + <li> + <button onclick="runManualTest(this, { requestPayerPhone: false }, { payerPhone: null })"> + payerPhone attribute is null when requestPayerPhone is false. + </button> + </li> + <li> + <button onclick="runManualTest(this, { requestPayerPhone: true }, { payerPhone: '+12345678910' })"> + payerPhone attribute is '+12345678910' when requestPayerPhone is true. + </button> + </li> + <li> + <button onclick="runManualTest(this, { requestPayerPhone: 'yep' }, { payerPhone: '+12345678910' }).then(done)"> + payerPhone attribute is '+12345678910' when requestPayerPhone is truthy. + </button> + </li> +</ol> +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-response/payerdetailschange-updateWith-immediate-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/payerdetailschange-updateWith-immediate-manual.https.html new file mode 100644 index 0000000000..7e35d78700 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/payerdetailschange-updateWith-immediate-manual.https.html @@ -0,0 +1,68 @@ +<!doctype html> +<meta charset=utf-8> +<title>Dispatching PaymentRequestUpdateEvent for "payerdetailschange"</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src="helpers.js"></script> +<script> +function testImmediateUpdate({ textContent: testName }) { + promise_test(async t => { + const response = await getPaymentResponse({ requestPayerName: true }); + const eventPromise = new Promise((resolve, reject) => { + response.addEventListener( + "payerdetailchange", + ev => { + // Forces updateWith() to be run in the next event loop tick so that + // [[waitForUpdate]] is already true when it runs. + t.step_timeout(() => { + try { + ev.updateWith({}); + resolve(); // This is bad. + } catch (err) { + reject(err); // this is good. + } + }); + }, + { once: true } + ); + }); + + const retryPromise = response.retry({ + payer: { name: "Change me!" }, + }); + await promise_rejects_dom( + t, + "InvalidStateError", + eventPromise, + "The event loop already spun, so [[waitForUpdate]] is now true" + ); + await retryPromise; + await response.complete("success"); + }, testName.trim()); +} +</script> +<h2>Handling PaymentResponse.prototype.onpayerdetailchange events</h2> +<p> + The test brings up the Payment Request UI window. + When shown the payment sheet, use any details and hit pay. +</p> +<p> + When asked to retry the payment: +</p> +<ol> + <li> + <p> + Change payer's name to anything. + </p> + <button onclick="testImmediateUpdate(this);"> + updateWith() must be called immediately, otherwise must throw an InvalidStateError. + </button> + </li> + <li> + <button onclick="done();">DONE!</button> + </li> +</ol> +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">owners</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-response/payerdetailschange-updateWith-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/payerdetailschange-updateWith-manual.https.html new file mode 100644 index 0000000000..1a7342365d --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/payerdetailschange-updateWith-manual.https.html @@ -0,0 +1,56 @@ +<!doctype html> +<meta charset=utf-8> +<title>Dispatching PaymentRequestUpdateEvent for "payerdetailschange"</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<script src="helpers.js"></script> +<script> +function runTest(button) { + button.disabled = true; + promise_test(async t => { + const response = await getPaymentResponse({ requestPayerName: true }); + const eventPromise = new Promise((_, reject) => { + response.addEventListener("payerdetailchange", ev => { + // [[waitForUpdate]] becomes true... + ev.updateWith({}); + // So calling it again throws "InvalidStateError". + try { + ev.updateWith({}); + } catch (err) { + reject(err); + } + }); + }); + await response.retry({ + payer: { name: "Change me!" }, + }); + await promise_rejects_dom(t, "InvalidStateError", eventPromise); + await response.complete("success"); + }, button.textContent.trim()); +} +</script> +<h2>Handling PaymentResponse.prototype.onpayerdetailchange events</h2> +<p> + The test brings up the Payment Request UI window. + When shown the payment sheet, use any details and hit pay. +</p> +<p> + When asked to retry the payment: +</p> +<ol> + <li> + <p> + Change payer's name to anything. + </p> + <button onclick="runTest(this);"> + Calling PaymentRequestUpdateEvent updateWith() twice throws an "InvalidStateError". + </button> + </li> + <li> + <button onclick="done();">DONE!</button> + </li> +</ol> +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">owners</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-response/rejects_if_not_active-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/rejects_if_not_active-manual.https.html new file mode 100644 index 0000000000..6f2e9e95d4 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/rejects_if_not_active-manual.https.html @@ -0,0 +1,160 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<link rel="help" href="https://w3c.github.io/payment-request/#retry-method"> +<title>PaymentResponse retry() rejects if doc is not fully active</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-retry"> +<body> +<script> +setup({ explicit_done: true, explicit_timeout: true }); +const validMethod = Object.freeze({ + supportedMethods: "basic-card", +}); +const applePay = Object.freeze({ + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + } +}); +const validMethods = Object.freeze([validMethod, applePay]); +const validAmount = Object.freeze({ + currency: "USD", + value: "5.00", +}); +const validTotal = Object.freeze({ + label: "Total due", + amount: validAmount, +}); +const validDetails = Object.freeze({ + total: validTotal, +}); + +function getLoadedPaymentResponse(iframe, url) { + return new Promise(resolve => { + iframe.addEventListener( + "load", + async () => { + const { PaymentRequest } = iframe.contentWindow; + const response = await new PaymentRequest( + validMethods, + validDetails + ).show(); + resolve(response); + }, + { once: true } + ); + iframe.src = url; + }); +} + +function methodNotFullyActive(button, method, ...args) { + const text = button.textContent.trim(); + promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.allow = "payment"; + document.body.appendChild(iframe); + + // We first got to page1.html, grab a PaymentResponse instance. + const response = await getLoadedPaymentResponse( + iframe, + "/payment-request/resources/page1.html" + ); + // We navigate the iframe again, putting response's document into an inactive state. + await new Promise(resolve => { + iframe.addEventListener("load", resolve); + iframe.src = "/payment-request/resources/page2.html"; + }); + // Now, response's relevant global object's document is no longer active. + // So, promise needs to reject appropriately. + const promise = response[methodName](...args); + await promise_rejects_dom( + t, + "AbortError", + promise, + "Inactive document, so must throw AbortError" + ); + // We are done, so clean up. + iframe.remove(); + }, text); +} + +function methodBecomesNotFullyActive(button, methodName, ...args) { + const text = button.textContent.trim(); + promise_test(async t => { + const iframe = document.createElement("iframe"); + iframe.allow = "payment"; + document.body.appendChild(iframe); + + // We first got to page1.html, grab a PaymentResponse instance. + const response = await getLoadedPaymentResponse( + iframe, + "/payment-request/resources/page1.html" + ); + + // we get the promise from page1.html, while it's active! + const promise = response[methodName](...args); + + // We navigate the iframe again, putting response's document into an inactive state. + await new Promise(resolve => { + iframe.addEventListener("load", resolve); + iframe.src = "/payment-request/resources/page2.html"; + }); + + // Now, response's relevant global object's document is no longer active. + // So, promise needs to reject appropriately. + await promise_rejects_dom( + t, + "AbortError", + promise, + "Inactive document, so must throw AbortError" + ); + // We are done, so clean up. + iframe.remove(); + }, text); +} +</script> +<section> + <p> + For each test, when the payment sheet is shown, select a payment method and hit "Pay". + </p> + <h2>retry() and document active state</h2> + <p>Manual Tests for PaymentResponse.retry() - Please run in order!</p> + <ol> + <li> + <button onclick="methodNotFullyActive(this, 'retry', {});"> + retry()'s retryPromise rejects if document is not fully active. + </button> + </li> + <li> + <button onclick="methodBecomesNotFullyActive(this, 'retry', {});"> + retry()'s retryPromise rejects if the document becomes not fully active. + </button> + </li> + </ol> + <h2>complete() and document active state</h2> + <p>Manual Tests for PaymentResponse.complete() - Please run in order!</p> + <ol> + <li> + <button onclick="methodNotFullyActive(this, 'complete', 'success');"> + complete()'s completePromise rejects if document is not fully active. + </button> + </li> + <li> + <button onclick="methodBecomesNotFullyActive(this, 'complete', 'success');"> + complete()'s completePromise rejects if the document becomes not fully active. + </button> + </li> + <li> + <button onclick="done();">Done</button> + </li> + </ol> +</section> +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/payment-response/requestId-attribute-manual.https.html b/testing/web-platform/tests/payment-request/payment-response/requestId-attribute-manual.https.html new file mode 100644 index 0000000000..ddb1e0d831 --- /dev/null +++ b/testing/web-platform/tests/payment-request/payment-response/requestId-attribute-manual.https.html @@ -0,0 +1,34 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://w3c.github.io/payment-request/#dom-paymentresponse-requestid"> +<title> + PaymentResponse.prototype.requestId attribute +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="helpers.js"></script> +<h2>requestId attribute</h2> +<p> + Click on each button in sequence from top to bottom without refreshing the page. + Each button will bring up the Payment Request UI window. +</p> +<p> + When presented with the payment sheet, use any credit card select to "Pay". + Also confirm any prompts that come up. +</p> +<ol> + <li> + <button onclick="runManualTest(this, {}, {})"> + Must mirror the payment request's automatically set id + </button> + </li> + <li> + <button onclick="runManualTest(this, {}, {requestId: 'pass'}, 'pass').then(done)"> + Must mirror the payment request's explicitly set id + </button> + </li> +</ol> +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>. +</small> diff --git a/testing/web-platform/tests/payment-request/rejects_if_not_active.https.html b/testing/web-platform/tests/payment-request/rejects_if_not_active.https.html new file mode 100644 index 0000000000..32feccb265 --- /dev/null +++ b/testing/web-platform/tests/payment-request/rejects_if_not_active.https.html @@ -0,0 +1,142 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>PaymentRequest show() rejects if doc is not fully active</title> +<link rel="help" href="https://w3c.github.io/payment-request/#show-method" /> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<body> + <script> + const applePay = Object.freeze({ + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + }, + }); + const validMethod = Object.freeze({ + supportedMethods: "basic-card", + }); + const validMethods = Object.freeze([validMethod, applePay]); + + const validDetails = Object.freeze({ + total: { + label: "Total due", + amount: { + currency: "USD", + value: "5.00", + }, + }, + }); + + async function getLoadedPaymentRequest(iframe, url) { + await new Promise((resolve) => { + iframe.addEventListener("load", resolve, { once: true }); + iframe.src = url; + }); + return new iframe.contentWindow.PaymentRequest( + validMethods, + validDetails + ); + } + + promise_test(async (t) => { + const iframe = document.createElement("iframe"); + iframe.allow = "payment"; + document.body.appendChild(iframe); + t.add_cleanup(() => { + iframe.remove(); + }); + // We first got to page1.html, grab a PaymentRequest instance. + const request1 = await getLoadedPaymentRequest( + iframe, + "./resources/page1.html" + ); + // Save the DOMException of page1.html before navigating away. + const frameDOMException1 = iframe.contentWindow.DOMException; + + // Get transient activation + await test_driver.bless("payment 1", () => {}, iframe.contentWindow); + + // We navigate the iframe again, putting request1's document into an non-fully active state. + const request2 = await getLoadedPaymentRequest( + iframe, + "./resources/page2.html" + ); + + // Now, request1's relevant global object's document is no longer active. + // So, call .show(), and make sure it rejects appropriately. + await promise_rejects_dom( + t, + "AbortError", + frameDOMException1, + request1.show(), + "Inactive document, so must throw AbortError" + ); + // request2 has an active document tho, so confirm it's working as expected: + // Get transient activation + await test_driver.bless("payment 2", () => {}, iframe.contentWindow); + request2.show(); + await request2.abort(); + await promise_rejects_dom( + t, + "InvalidStateError", + iframe.contentWindow.DOMException, + request2.show(), + "Abort already called, so InvalidStateError" + ); + }, "PaymentRequest.show() aborts if the document is not active."); + + promise_test(async (t) => { + // We nest two iframes and wait for them to load. + const outerIframe = document.createElement("iframe"); + outerIframe.allow = "payment"; + document.body.appendChild(outerIframe); + t.add_cleanup(() => { + outerIframe.remove(); + }); + // Load the outer iframe (we don't care about the awaited request) + await getLoadedPaymentRequest(outerIframe, "./resources/page1.html"); + + // Now we create the inner iframe + const innerIframe = outerIframe.contentDocument.createElement("iframe"); + innerIframe.allow = "payment"; + + // nest them + outerIframe.contentDocument.body.appendChild(innerIframe); + + // load innerIframe, and get the PaymentRequest instance + const request = await getLoadedPaymentRequest( + innerIframe, + "./resources/page2.html" + ); + // Save DOMException from innerIframe before navigating away. + const innerIframeDOMException = innerIframe.contentWindow.DOMException; + + // Navigate the outer iframe to a new location. + // Wait for the load event to fire. + await new Promise((resolve) => { + outerIframe.addEventListener("load", resolve); + outerIframe.src = "./resources/page2.html"; + }); + + await test_driver.bless("", () => {}, innerIframe.contentWindow); + const showPromise = request.show(); + // Now, request's relevant global object's document is still active + // (it is the active document of the inner iframe), but is not fully active + // (since the parent of the inner iframe is itself no longer active). + // So, call request.show() and make sure it rejects appropriately. + await promise_rejects_dom( + t, + "AbortError", + innerIframeDOMException, + showPromise, + "Active, but not fully active, so must throw AbortError" + ); + }, "PaymentRequest.show() aborts if the document is active, but not fully active."); + </script> +</body> diff --git a/testing/web-platform/tests/payment-request/resources/delegate-request-subframe.sub.html b/testing/web-platform/tests/payment-request/resources/delegate-request-subframe.sub.html new file mode 100644 index 0000000000..aeda1f00d4 --- /dev/null +++ b/testing/web-platform/tests/payment-request/resources/delegate-request-subframe.sub.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<title>Payment request delegation test: subframe</title> + +<script> + function reportResult(msg) { + window.top.postMessage({"type": "result", "result": msg}, "*"); + } + + async function requestPayment(e) { + const supportedMethods = [{ + supportedMethods: "https://{{hosts[][nonexistent]}}/payment-request" + }]; + const details = { + total: { label: "Test", amount: { currency: "CAD", value: "1.00" } } + }; + const request = new PaymentRequest(supportedMethods, details); + + request.show().catch(e => { + if (e.name == "SecurityError") { + reportResult("failure"); + } else if (e.name == "NotSupportedError") { + // Because we used a non-existent url in supportedMethod aboves, this error message + // means all checks required for this test (i.e. user activation check and payment + // delegation check) have passed successfully. + reportResult("success"); + } else { + reportResult("unexpected"); + } + }); + } + + window.addEventListener("message", e => { + if (e.data.type == "make-payment-request") + requestPayment(); + }); + + window.top.postMessage({"type": "subframe-loaded"}, "*"); +</script> diff --git a/testing/web-platform/tests/payment-request/resources/page1.html b/testing/web-platform/tests/payment-request/resources/page1.html new file mode 100644 index 0000000000..7fc080d380 --- /dev/null +++ b/testing/web-platform/tests/payment-request/resources/page1.html @@ -0,0 +1 @@ +<meta charset="utf-8"> diff --git a/testing/web-platform/tests/payment-request/resources/page2.html b/testing/web-platform/tests/payment-request/resources/page2.html new file mode 100644 index 0000000000..7fc080d380 --- /dev/null +++ b/testing/web-platform/tests/payment-request/resources/page2.html @@ -0,0 +1 @@ +<meta charset="utf-8"> diff --git a/testing/web-platform/tests/payment-request/show-consume-activation.https.html b/testing/web-platform/tests/payment-request/show-consume-activation.https.html new file mode 100644 index 0000000000..6f629489e4 --- /dev/null +++ b/testing/web-platform/tests/payment-request/show-consume-activation.https.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <title>show() consumes user activation</title> + <link rel="help" href="https://w3c.github.io/payment-request/#show-method" /> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + </head> + <body> + <script> + const defaultMethods = [ + { supportedMethods: "basic-card" }, + { + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + }, + }, + ]; + + const defaultDetails = { + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, + }; + promise_test(async t => { + const pr = new PaymentRequest(defaultMethods, defaultDetails); + + await test_driver.bless("Calls show() method"); + const showPromise = pr.show(); + + // The activation has been consumed. + assert_false(navigator.userActivation.isActive); + + // Abort the payment request + pr.abort() + await promise_rejects_dom(t, "AbortError", showPromise); + }, "Calling show consumes user activation, if present"); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/payment-request/show-method-optional-promise-rejects.https.html b/testing/web-platform/tests/payment-request/show-method-optional-promise-rejects.https.html new file mode 100644 index 0000000000..4a41f28fc9 --- /dev/null +++ b/testing/web-platform/tests/payment-request/show-method-optional-promise-rejects.https.html @@ -0,0 +1,215 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>Test for PaymentRequest.show(optional detailsPromise) method</title> +<link + rel="help" + href="https://w3c.github.io/browser-payment-api/#show-method" +/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script> + // See function testBadUpdate() for test details! + setup({ + allow_uncaught_exception: true, + }); + + // == TEST DATA === + // PaymentMethod + const validMethod = Object.freeze({ + supportedMethods: "valid-but-wont-ever-match", + }); + + const validMethodBasicCard = Object.freeze({ + supportedMethods: "basic-card", + }); + + const validMethodApplePay = Object.freeze({ + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + }, + }); + + // Methods + const validMethods = Object.freeze([ + validMethodBasicCard, + validMethod, + validMethodApplePay, + ]); + + // Amounts + const validAmount = Object.freeze({ + currency: "USD", + value: "1.00", + }); + + const invalidAmount = Object.freeze({ + currency: "¡INVALID!", + value: "A1.0", + }); + + const negativeAmount = Object.freeze({ + currency: "USD", + value: "-1.00", + }); + + // Totals + const validTotal = Object.freeze({ + label: "Valid Total", + amount: validAmount, + }); + + const invalidTotal = Object.freeze({ + label: "Invalid Total", + amount: invalidAmount, + }); + + const invalidNegativeTotal = Object.freeze({ + label: "Invalid negative total", + amount: negativeAmount, + }); + + // PaymentDetailsInit + const validDetails = Object.freeze({ + total: validTotal, + }); + + const invalidDetailsNegativeTotal = Object.freeze({ + total: invalidNegativeTotal, + }); + + + // PaymentItem + const validPaymentItem = Object.freeze({ + amount: validAmount, + label: "Valid payment item", + }); + + const invalidPaymentItem = Object.freeze({ + amount: invalidAmount, + label: "Invalid payment item", + }); + + // PaymentItem + const validPaymentItems = Object.freeze([validPaymentItem]); + const invalidPaymentItems = Object.freeze([invalidPaymentItem]); + + // PaymentDetailsModifier + const validModifier = Object.freeze({ + additionalDisplayItems: validPaymentItems, + supportedMethods: "valid-but-wont-ever-match", + total: validTotal, + }); + + const modifierWithInvalidDisplayItems = Object.freeze({ + additionalDisplayItems: invalidPaymentItems, + supportedMethods: "basic-card", + total: validTotal, + }); + + const modifierWithValidDisplayItems = Object.freeze({ + additionalDisplayItems: validPaymentItems, + supportedMethods: "basic-card", + total: validTotal, + }); + + const modifierWithInvalidTotal = Object.freeze({ + additionalDisplayItems: validPaymentItems, + supportedMethods: "basic-card", + total: invalidTotal, + }); + + const recursiveData = {}; + recursiveData.foo = recursiveData; + Object.freeze(recursiveData); + + const modifierWithRecursiveData = Object.freeze({ + supportedMethods: "basic-card", + total: validTotal, + data: recursiveData, + }); + // == END OF TEST DATA === + /* + These test work by creating a "valid" payment request and then + performing a bad update via `show(detailsPromise)`. + The `badDetails` cause detailsPromise to reject with `expectedError`. + */ + function testBadUpdate(testAssertion, badDetails, expectedError) { + promise_test(async (t) => { + const request = new PaymentRequest( + validMethods, + validDetails + ); + await test_driver.bless("Payment request"); + const detailsPromise = Promise.resolve(badDetails); + const acceptPromise = request.show(detailsPromise); + const test_func = + typeof expectedError == "function" + ? promise_rejects_js + : promise_rejects_dom; + await test_func( + t, + expectedError, + acceptPromise, + "badDetails must cause acceptPromise to reject with expectedError" + ); + }, testAssertion); + } + + const rejectedPromise = Promise.reject(new SyntaxError("test")); + testBadUpdate( + "Rejection of detailsPromise must abort the update with an 'AbortError' DOMException.", + rejectedPromise, + "AbortError" + ); + + testBadUpdate( + "Total in the update is a string, so converting to IDL must abort the update with a TypeError.", + { total: `this will cause a TypeError!` }, + TypeError + ); + + testBadUpdate( + "Total is recursive, so converting to IDL must abort the update with a TypeError.", + { total: recursiveData }, + TypeError + ); + + testBadUpdate( + "Updating with a negative total results in a TypeError.", + invalidDetailsNegativeTotal, + TypeError + ); + + testBadUpdate( + "Updating with a displayItem with an invalid currency results in RangeError.", + { ...validDetails, displayItems: invalidPaymentItems }, + RangeError + ); + + testBadUpdate( + "Must throw a RangeError when a modifier's total item has an invalid currency.", + { ...validDetails, modifiers: [modifierWithInvalidTotal, validModifier] }, + RangeError + ); + + testBadUpdate( + "Must throw a RangeError when a modifier display item has an invalid currency.", + { + ...validDetails, + modifiers: [modifierWithInvalidDisplayItems, validModifier], + }, + RangeError + ); + testBadUpdate( + "Must throw as Modifier has a recursive dictionary.", + { ...validDetails, modifiers: [modifierWithRecursiveData, validModifier] }, + TypeError + ); +</script> diff --git a/testing/web-platform/tests/payment-request/show-method-postmessage-iframe.html b/testing/web-platform/tests/payment-request/show-method-postmessage-iframe.html new file mode 100644 index 0000000000..b50f18ecce --- /dev/null +++ b/testing/web-platform/tests/payment-request/show-method-postmessage-iframe.html @@ -0,0 +1,49 @@ +<h1>This iframe calls shows() via postMessage()</h1> +<script> +"use strict"; +const defaultMethods = Object.freeze([ + { supportedMethods: "basic-card" }, + { + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + } + }, +]); + +const defaultDetails = Object.freeze({ + id: "fail", + total: { + label: "Total", + amount: { + currency: "USD", + value: "1.00", + }, + }, +}); + +// We are going to use the id to prove that this works +// which we will pass back to the caller +window.onmessage = async event => { + const { source, data: { id, request } } = event; + switch (request) { + case "show-payment-request": { + const details = Object.assign({}, defaultDetails, { id }); + const request = new PaymentRequest(defaultMethods, details); + try { + const response = await request.show(); + source.postMessage(response.toJSON(), window.location.origin); + await response.complete(); + } catch (err) { + source.postMessage({ requestId: "fail" }, window.location.origin); + await request.abort(); + } + } + } +}; + +</script> diff --git a/testing/web-platform/tests/payment-request/show-method-postmessage-manual.https.html b/testing/web-platform/tests/payment-request/show-method-postmessage-manual.https.html new file mode 100644 index 0000000000..0751920e37 --- /dev/null +++ b/testing/web-platform/tests/payment-request/show-method-postmessage-manual.https.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Test for PaymentRequest.show() method</title> +<link rel="help" href="https://w3c.github.io/browser-payment-api/#show-method"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +"use strict"; +setup({ + explicit_done: true, + explicit_timeout: true, + allow_uncaught_exception: true, +}); + +async function runUserActivation(button) { + button.disabled = true; + const { contentWindow: iframeWindow } = document.getElementById("iframe"); + const expectedId = "pass123"; + await Promise.resolve(); // next tick + const promiseForResponse = new Promise(resolve => { + window.onmessage = ({ data: { requestId } }) => resolve(requestId); + }); + const ops = { id: expectedId, request: "show-payment-request" }; + iframeWindow.postMessage(ops, window.location.origin); + promise_test(async () => { + const actualId = await promiseForResponse; + assert_equals(actualId, expectedId, "ids must match"); + }, button.textContent.trim()); + done(); +} + +</script> +<h2>Test PaymentRequest.show() triggered by user activation using postMessage()</h2> +<p> + Tests that user activation works over postMessage(). +</p> +<p> + Click on bottom below. Hit "Pay". +</p> +<ol> + <li> + <button onclick="runUserActivation(this)"> + show() is triggered by user activation passed through postMessage() and a promise + </button> + </li> +</ol> +<iframe width="100%" id="iframe" src="show-method-postmessage-iframe.html" allow="payment"></iframe> +<p> + <small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>. + </small> +</p> diff --git a/testing/web-platform/tests/payment-request/user-abort-algorithm-manual.https.html b/testing/web-platform/tests/payment-request/user-abort-algorithm-manual.https.html new file mode 100644 index 0000000000..078bf3d61a --- /dev/null +++ b/testing/web-platform/tests/payment-request/user-abort-algorithm-manual.https.html @@ -0,0 +1,80 @@ +<!doctype html> +<meta charset="utf8"> +<link rel="help" href="https://w3c.github.io/payment-request/#user-aborts-the-payment-request-algorithm"> +<title> + User aborts the payment request algorithm. +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +setup({ explicit_done: true, explicit_timeout: true }); + +const validAmount = Object.freeze({ + currency: "USD", + value: "1.0", +}); +const validTotal = Object.freeze({ + label: "Total due", + amount: validAmount, +}); +const applePay = Object.freeze({ + supportedMethods: "https://apple.com/apple-pay", + data: { + version: 3, + merchantIdentifier: "merchant.com.example", + countryCode: "US", + merchantCapabilities: ["supports3DS"], + supportedNetworks: ["visa"], + } +}); +const validMethod = Object.freeze({ + supportedMethods: "basic-card", +}); +const validMethods = Object.freeze([validMethod, applePay]); +const validDetails = Object.freeze({ + total: validTotal, +}); + +test(() => { + try { + new PaymentRequest(validMethods, validDetails); + } catch (err) { + done(); + throw err; + } +}, "Can construct a payment request (smoke test)."); + +async function runAbortTest(button) { + button.disabled = true; + const { textContent: testName } = button; + promise_test(async t => { + const request = new PaymentRequest(validMethods, validDetails); + // Await the user to abort + await promise_rejects_dom(t, "AbortError", request.show()); + // [[state]] is now closed + await promise_rejects_dom(t, "InvalidStateError", request.show()); + }, testName.trim()); +} +</script> +<h2> + User aborts the payment request algorithm. +</h2> +<p> + Click on each button in sequence from top to bottom without refreshing the page. + Each button will bring up the Payment Request UI window. +</p> +<p> + When presented with the payment sheet, abort the payment request + (e.g., by hitting the esc key or pressing a UA provided button). +</p> +<ol> + <li> + <button onclick="runAbortTest(this); done();"> + If the user aborts, the UA must run the user aborts the payment request algorithm. + </button> + </li> +</ol> +<small> + If you find a buggy test, please <a href="https://github.com/web-platform-tests/wpt/issues">file a bug</a> + and tag one of the <a href="https://github.com/web-platform-tests/wpt/blob/master/payment-request/META.yml">suggested reviewers</a>. +</small> |