diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/payment-handler | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/payment-handler')
29 files changed, 1992 insertions, 0 deletions
diff --git a/testing/web-platform/tests/payment-handler/META.yml b/testing/web-platform/tests/payment-handler/META.yml new file mode 100644 index 0000000000..eff7624d05 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/META.yml @@ -0,0 +1,4 @@ +spec: https://w3c.github.io/payment-handler/ +suggested_reviewers: + - marcoscaceres + - rsolomakhin diff --git a/testing/web-platform/tests/payment-handler/app-can-make-payment.js b/testing/web-platform/tests/payment-handler/app-can-make-payment.js new file mode 100644 index 0000000000..cd27bce02e --- /dev/null +++ b/testing/web-platform/tests/payment-handler/app-can-make-payment.js @@ -0,0 +1,58 @@ +let responseType = 'canMakePayment-true'; +self.addEventListener('message', event => { + responseType = event.data.responseType; +}); + +self.addEventListener('canmakepayment', event => { + if (event.methodData) { + const msg = 'Expected no method data.'; + event.respondWith(Promise.reject(new Error(msg))); + return; + } + + if (event.modifiers) { + const msg = 'Expected no modifiers'; + event.respondWith(Promise.reject(new Error(msg))); + return; + } + + if (event.topOrigin) { + const msg = `Unexpected topOrigin.`; + event.respondWith(Promise.reject(new Error(msg))); + return; + } + + if (event.paymentRequestOrigin) { + const msg = `Unexpected iframe origin.`; + event.respondWith(Promise.reject(new Error(msg))); + return; + } + + switch (responseType) { + case 'canMakePayment-true': + event.respondWith(true); + break; + case 'canMakePayment-false': + event.respondWith(false); + break; + case 'canMakePayment-promise-true': + event.respondWith(Promise.resolve(true)); + break; + case 'canMakePayment-promise-false': + event.respondWith(Promise.resolve(false)); + break; + case 'canMakePayment-custom-error': + event.respondWith(Promise.reject(new Error('Custom error'))); + break; + default: + const msg = `Unrecognized payment method name "${methodName}".`; + event.respondWith(Promise.reject(new Error(msg))); + break; + } +}); + +// Respond 'true' to the 'abortpayment' event to allow tests to use abort() to +// close an ongoing PaymentRequest. +self.addEventListener('abortpayment', event => { + event.respondWith(true); +}); diff --git a/testing/web-platform/tests/payment-handler/app-change-payment-method.js b/testing/web-platform/tests/payment-handler/app-change-payment-method.js new file mode 100644 index 0000000000..0e5a4768e7 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/app-change-payment-method.js @@ -0,0 +1,30 @@ +self.addEventListener('canmakepayment', (event) => { + event.respondWith(true); +}); + +async function responder(event) { + const methodName = event.methodData[0].supportedMethods; + if (!event.changePaymentMethod) { + return { + methodName, + details: { + changePaymentMethodReturned: + 'The changePaymentMethod() method is not implemented.', + }, + }; + } + let changePaymentMethodReturned; + try { + const response = await event.changePaymentMethod(methodName, { + country: 'US', + }); + changePaymentMethodReturned = response; + } catch (err) { + changePaymentMethodReturned = err.message; + } + return {methodName, details: {changePaymentMethodReturned}}; +} + +self.addEventListener('paymentrequest', (event) => { + event.respondWith(responder(event)); +}); diff --git a/testing/web-platform/tests/payment-handler/app-change-shipping-address.js b/testing/web-platform/tests/payment-handler/app-change-shipping-address.js new file mode 100644 index 0000000000..df39258dc9 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/app-change-shipping-address.js @@ -0,0 +1,44 @@ +self.addEventListener('canmakepayment', (event) => { + event.respondWith(true); +}); + +async function responder(event) { + const methodName = event.methodData[0].supportedMethods; + const shippingOption = event.shippingOptions[0].id; + const shippingAddress = { + addressLine: [ + '1875 Explorer St #1000', + ], + city: 'Reston', + country: 'US', + dependentLocality: '', + organization: 'Google', + phone: '+15555555555', + postalCode: '20190', + recipient: 'John Smith', + region: 'VA', + sortingCode: '', + }; + if (!event.changeShippingAddress) { + return { + methodName, + details: { + changeShippingAddressReturned: + 'The changeShippingAddress() method is not implemented.', + }, + }; + } + let changeShippingAddressReturned; + try { + const response = await event.changeShippingAddress(shippingAddress); + changeShippingAddressReturned = response; + } catch (err) { + changeShippingAddressReturned = err.message; + } + return {methodName, details: {changeShippingAddressReturned}, shippingAddress, + shippingOption}; +} + +self.addEventListener('paymentrequest', (event) => { + event.respondWith(responder(event)); +}); diff --git a/testing/web-platform/tests/payment-handler/app-change-shipping-option.js b/testing/web-platform/tests/payment-handler/app-change-shipping-option.js new file mode 100644 index 0000000000..ac3307b619 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/app-change-shipping-option.js @@ -0,0 +1,44 @@ +self.addEventListener('canmakepayment', (event) => { + event.respondWith(true); +}); + +async function responder(event) { + const methodName = event.methodData[0].supportedMethods; + const shippingOption = event.shippingOptions[0].id; + const shippingAddress = { + addressLine: [ + '1875 Explorer St #1000', + ], + city: 'Reston', + country: 'US', + dependentLocality: '', + organization: 'Google', + phone: '+15555555555', + postalCode: '20190', + recipient: 'John Smith', + region: 'VA', + sortingCode: '', + }; + if (!event.changeShippingOption) { + return { + methodName, + details: { + changeShippingOptionReturned: + 'The changeShippingOption() method is not implemented.', + }, + }; + } + let changeShippingOptionReturned; + try { + const response = await event.changeShippingOption(shippingOption); + changeShippingOptionReturned = response; + } catch (err) { + changeShippingOptionReturned = err.message; + } + return {methodName, details: {changeShippingOptionReturned}, shippingAddress, + shippingOption}; +} + +self.addEventListener('paymentrequest', (event) => { + event.respondWith(responder(event)); +}); diff --git a/testing/web-platform/tests/payment-handler/app-simple.js b/testing/web-platform/tests/payment-handler/app-simple.js new file mode 100644 index 0000000000..833a01f47e --- /dev/null +++ b/testing/web-platform/tests/payment-handler/app-simple.js @@ -0,0 +1,74 @@ +self.addEventListener('paymentrequest', event => { + const expectedId = 'test-payment-request-identifier'; + if (event.paymentRequestId !== expectedId) { + const msg = `Expected payment request identifier "${expectedId}", but got "${ + event.paymentRequestId + }"`; + event.respondWith(Promise.reject(new Error(msg))); + return; + } + + if (event.methodData.length !== 1) { + const msg = `Expected one method data, but got ${ + event.methodData.length + } instead`; + event.respondWith(Promise.reject(new Error(msg))); + return; + } + + const methodData = event.methodData[0]; + const expectedMethodName = window.location.origin + '/payment-handler/payment-app/'; + if (methodData.supportedMethods !== expectedMethodName) { + const msg = `Expected payment method name "${expectedMethodName}", but got "${ + methodData.supportedMethods + }"`; + event.respondWith(Promise.reject(new Error(msg))); + return; + } + + if (methodData.data.supportedNetworks) { + const msg = + 'Expected no supported networks in payment method specific data'; + event.respondWith(Promise.reject(new Error(msg))); + return; + } + + if (methodData.displayItems) { + const msg = 'Expected no display items'; + event.respondWith(Promise.reject(new Error(msg))); + return; + } + + const total = event.total; + if (!total) { + const msg = 'Expected total'; + event.respondWith(Promise.reject(new Error(msg))); + return; + } + + if (total.label) { + const msg = 'Expected no total label'; + event.respondWith(Promise.reject(new Error(msg))); + return; + } + + const expectedCurrency = 'USD'; + if (total.currency !== expectedCurrency) { + const msg = `Expected currency "${expectedCurrency}", but got "${ + total.currency + }"`; + event.respondWith(Promise.reject(new Error(msg))); + return; + } + + const expectedValue = '0.01'; + if (total.value !== expectedValue) { + const msg = `Expected value "${expectedValue}", but got "${total.value}"`; + event.respondWith(Promise.reject(new Error(msg))); + return; + } + + event.respondWith({ + methodName: expectedMethodName, + }); +}); diff --git a/testing/web-platform/tests/payment-handler/app-supports-shipping-contact-delegation.js b/testing/web-platform/tests/payment-handler/app-supports-shipping-contact-delegation.js new file mode 100644 index 0000000000..770e2de64f --- /dev/null +++ b/testing/web-platform/tests/payment-handler/app-supports-shipping-contact-delegation.js @@ -0,0 +1,44 @@ +self.addEventListener('canmakepayment', (event) => { + event.respondWith(true); +}); + +function responder(event) { + const methodName = event.methodData[0].supportedMethods; + const shippingOption = event.paymentOptions.requestShipping + ? event.shippingOptions[0].id + : ''; + const payerName = + event.paymentOptions.requestPayerName ? 'John Smith' : ''; + const payerEmail = + event.paymentOptions.requestPayerEmail ? 'smith@gmail.com' : ''; + const payerPhone = + event.paymentOptions.requestPayerPhone ? '+15555555555' : ''; + const shippingAddress = event.paymentOptions.requestShipping ? { + addressLine: [ + '1875 Explorer St #1000', + ], + city: 'Reston', + country: 'US', + dependentLocality: '', + organization: 'Google', + phone: '+15555555555', + postalCode: '20190', + recipient: 'John Smith', + region: 'VA', + sortingCode: '', + } : {}; + + return { + methodName, + details: { token: '123456789'}, + payerName, + payerEmail, + payerPhone, + shippingAddress, + shippingOption + }; +} + +self.addEventListener('paymentrequest', (event) => { + event.respondWith(responder(event)); +}); diff --git a/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.html b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.html new file mode 100644 index 0000000000..6892f01aa9 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.html @@ -0,0 +1,14 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Test for CanMakePaymentEvent Constructor</title> +<link rel="help" href="https://w3c.github.io/payment-handler/#dom-canmakepaymentevent"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> + +test(() => { + assert_false('CanMakePaymentEvent' in window); +}, 'CanMakePaymentEvent constructor must not be exposed in window'); + +</script> diff --git a/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.serviceworker.html b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.serviceworker.html new file mode 100644 index 0000000000..afff850dc4 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.serviceworker.html @@ -0,0 +1,13 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Test for CanMakePaymentEvent Constructor</title> +<link rel="help" href="https://w3c.github.io/payment-handler/#dom-canmakepaymentevent"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> + +service_worker_test('can-make-payment-event-constructor.https.serviceworker.js', + 'CanMakePaymentEvent can be constructed in service worker'); + +</script> diff --git a/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.serviceworker.js b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.serviceworker.js new file mode 100644 index 0000000000..5b334d9c3a --- /dev/null +++ b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.serviceworker.js @@ -0,0 +1,47 @@ +importScripts('/resources/testharness.js'); + +test(() => { + try { + new CanMakePaymentEvent('test', undefined); + new CanMakePaymentEvent('test', null); + new CanMakePaymentEvent('test', {}); + } catch (err) { + assert_unreached(`Unexpected exception: ${err.message}`); + } +}, 'CanMakePaymentEvent can be constructed in service worker.'); + +test(() => { + const ev = new CanMakePaymentEvent('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'); +}, 'CanMakePaymentEvent can be constructed with an EventInitDict, even if not trusted'); + +test(() => { + const ev = new CanMakePaymentEvent('test', { + topOrigin: 'https://foo.com', + paymentRequestOrigin: 'https://bar.com', + methodData: [], + modifiers: [], + }); + assert_false(ev.isTrusted, 'constructed in script, so not be trusted'); + assert_equals(ev.topOrigin, undefined); + assert_equals(ev.paymentRequestOrigin, undefined); + assert_equals(ev.methodData, undefined); + assert_equals(ev.modifiers, undefined); +}, 'CanMakePaymentEvent can be constructed with a CanMakePaymentEventInit, even if not trusted'); + +test(() => { + const ev = new CanMakePaymentEvent('test', {}); + self.addEventListener('test', evt => { + assert_equals(ev, evt); + }); + self.dispatchEvent(ev); +}, 'CanMakePaymentEvent can be dispatched, even if not trusted'); diff --git a/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.worker.js b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.worker.js new file mode 100644 index 0000000000..d88bddceaf --- /dev/null +++ b/testing/web-platform/tests/payment-handler/can-make-payment-event-constructor.https.worker.js @@ -0,0 +1,7 @@ +importScripts('/resources/testharness.js'); + +test(() => { + assert_false('CanMakePaymentEvent' in self); +}, 'CanMakePaymentEvent constructor must not be exposed in worker'); + +done(); diff --git a/testing/web-platform/tests/payment-handler/can-make-payment-event.https.html b/testing/web-platform/tests/payment-handler/can-make-payment-event.https.html new file mode 100644 index 0000000000..941c206e3b --- /dev/null +++ b/testing/web-platform/tests/payment-handler/can-make-payment-event.https.html @@ -0,0 +1,264 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Tests for CanMakePaymentEvent</title> +<link rel="help" href="https://w3c.github.io/payment-handler/#the-canmakepaymentevent"> +<link rel="manifest" href="manifest.json"> +<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 instrumentKey = 'instrument-key'; + +async function registerApp(methodName) { + await navigator.serviceWorker.register('app-can-make-payment.js'); + const registration = await navigator.serviceWorker.ready; + if (!registration.paymentManager) { + return; + } + if (registration.paymentManager.requestPermission) { + const permission = await registration.paymentManager.requestPermission(); + if (permission !== 'granted') { + return; + } + } + await registration.paymentManager.instruments.set(instrumentKey, { + name: 'Test Payment Method', + method: methodName, + }); + return registration; +} + +function buildPaymentRequest(methodName) { + const unsupportedMethodName = methodName + '/unsupported'; + return new PaymentRequest( + [ + { + supportedMethods: methodName, + data: { + defaultParameter: 'defaultValue', + }, + }, + { + supportedMethods: unsupportedMethodName, + data: { + defaultUnsupportedParameter: 'defaultUnsupportedValue', + }, + }, + ], + { + total: { + label: 'Total', + amount: { + currency: 'USD', + value: '0', + }, + }, + displayItems: [ + { + label: 'Nada', + amount: {currency: 'USD', value: '0'}, + }, + ], + modifiers: [ + { + supportedMethods: [methodName], + data: { + modifiedParameter: 'modifiedValue', + }, + total: { + label: 'Modified Total', + amount: { + currency: 'USD', + value: '0.0001', + }, + }, + additionalDisplayItems: [ + { + label: 'Something', + amount: {currency: 'USD', value: '0.0001'}, + }, + ], + }, + { + supportedMethods: [unsupportedMethodName], + data: { + modifiedUnsupportedParameter: 'modifiedUnsupportedValue', + }, + total: { + label: 'Modified Unsupported Total', + amount: { + currency: 'USD', + value: '10', + }, + }, + additionalDisplayItems: [ + { + label: 'Something Unsupported', + amount: {currency: 'USD', value: '10'}, + }, + ], + }, + ], + }, + ); +} + +promise_test(async t => { + const methodName = window.location.origin; + // Intentionally do not install the payment app. + const request = buildPaymentRequest(methodName); + assert_not_equals(request, undefined); + let paymentRequestCanMakePaymentResult; + try { + paymentRequestCanMakePaymentResult = await request.canMakePayment(); + } catch (err) { + assert_equals( + err.name, + 'NotAllowedError', + 'If it throws, then it must be NotAllowedError', + ); + } + assert_false( + paymentRequestCanMakePaymentResult, + 'canMakePayment() must return false.', + ); + + await test_driver.bless('PaymentRequest.show() requires user activation'); + await promise_rejects_dom(t, 'NotSupportedError', request.show()); +}, 'If a payment handler is not installed, then the payment method is not supported.'); + +promise_test(async t => { + const methodName = window.location.origin; + await registerApp(methodName); + navigator.serviceWorker.controller.postMessage( + {responseType: 'canMakePayment-false'}); + const request = buildPaymentRequest(methodName); + assert_not_equals(request, undefined); + let paymentRequestCanMakePaymentResult; + try { + paymentRequestCanMakePaymentResult = await request.canMakePayment(); + } catch (err) { + assert_equals( + err.name, + 'NotAllowedError', + 'If it throws, then it must be NotAllowedError', + ); + } + assert_false( + paymentRequestCanMakePaymentResult, + 'canMakePayment() must return false.', + ); + + await test_driver.bless('PaymentRequest.show() requires user activation'); + await promise_rejects_dom(t, 'NotSupportedError', request.show()); +}, 'If CanMakePaymentEvent.respondWith(false) is called, then the payment method is not supported.'); + +promise_test(async t => { + const methodName = window.location.origin; + await registerApp(methodName); + navigator.serviceWorker.controller.postMessage( + {responseType: 'canMakePayment-promise-false'}); + const request = buildPaymentRequest(methodName); + assert_not_equals(request, undefined); + let paymentRequestCanMakePaymentResult; + try { + paymentRequestCanMakePaymentResult = await request.canMakePayment(); + } catch (err) { + assert_equals( + err.name, + 'NotAllowedError', + 'If it throws, then it must be NotAllowedError', + ); + } + assert_false( + paymentRequestCanMakePaymentResult, + 'canMakePayment() must return false.', + ); + + await test_driver.bless('PaymentRequest.show() requires user activation'); + await promise_rejects_dom(t, 'NotSupportedError', request.show()); +}, 'If CanMakePaymentEvent.respondWith(Promise.resolve(false)) is called, then the payment method is not supported.'); + +promise_test(async t => { + const methodName = window.location.origin; + await registerApp(methodName); + navigator.serviceWorker.controller.postMessage( + {responseType: 'canMakePayment-true'}); + const request = buildPaymentRequest(methodName); + assert_not_equals(request, undefined); + let paymentRequestCanMakePaymentResult; + try { + paymentRequestCanMakePaymentResult = await request.canMakePayment(); + } catch (err) { + assert_equals( + err.name, + 'NotAllowedError', + 'If it throws, then it must be NotAllowedError', + ); + } + assert_true( + paymentRequestCanMakePaymentResult, + 'canMakePayment() must return true.', + ); + + await test_driver.bless('PaymentRequest.show() requires user activation'); + const acceptPromise = request.show(); + await request.abort(); + await promise_rejects_dom(t, 'AbortError', acceptPromise); +}, 'If CanMakePaymentEvent.respondWith(true) is called, then the payment method is supported.'); + +promise_test(async t => { + const methodName = window.location.origin; + await registerApp(methodName); + navigator.serviceWorker.controller.postMessage( + {responseType: 'canMakePayment-promise-true'}); + const request = buildPaymentRequest(methodName); + assert_not_equals(request, undefined); + let paymentRequestCanMakePaymentResult; + try { + paymentRequestCanMakePaymentResult = await request.canMakePayment(); + } catch (err) { + assert_equals( + err.name, + 'NotAllowedError', + 'If it throws, then it must be NotAllowedError', + ); + } + assert_true( + paymentRequestCanMakePaymentResult, + 'canMakePayment() must return true.', + ); + + await test_driver.bless('PaymentRequest.show() requires user activation'); + const acceptPromise = request.show(); + await request.abort(); + await promise_rejects_dom(t, 'AbortError', acceptPromise); +}, 'If CanMakePaymentEvent.respondWith(Promise.resolve(true)) is called, then the payment method is supported.'); + +promise_test(async t => { + const methodName = window.location.origin; + await registerApp(methodName); + navigator.serviceWorker.controller.postMessage( + {responseType: 'canMakePayment-custom-error'}); + const request = buildPaymentRequest(methodName); + assert_not_equals(request, undefined); + let paymentRequestCanMakePaymentResult; + try { + paymentRequestCanMakePaymentResult = await request.canMakePayment(); + } catch (err) { + assert_equals( + err.name, + 'NotAllowedError', + 'If it throws, then it must be NotAllowedError', + ); + } + assert_false( + paymentRequestCanMakePaymentResult, + 'canMakePayment() must return false.', + ); + + await test_driver.bless('PaymentRequest.show() requires user activation'); + await promise_rejects_dom(t, 'NotSupportedError', request.show()); +}, 'If CanMakePaymentEvent.respondWith(Promise.reject(error)) is called, then the payment method is not supported.'); +</script> diff --git a/testing/web-platform/tests/payment-handler/change-payment-method-manual.https.html b/testing/web-platform/tests/payment-handler/change-payment-method-manual.https.html new file mode 100644 index 0000000000..1640420c62 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/change-payment-method-manual.https.html @@ -0,0 +1,170 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>Tests for PaymentRequestEvent.changePaymentMethod()</title> +<link + rel="help" + href="https://w3c.github.io/payment-handler/#changepaymentmethod-method" +/> +<link rel="manifest" href="/payment-handler/manifest.json" /> +<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 src="register-and-activate-service-worker.js"></script> +<p>If the payment sheet is shown, please authorize the mock payment.</p> +<script> + async function runTests(registration) { + const methodName = window.location.origin + '/payment-handler/payment-app/'; + await registration.paymentManager.instruments.clear(); + await registration.paymentManager.instruments.set('instrument-key', { + name: 'Instrument Name', + method: methodName, + }); + + promise_test(async (t) => { + const request = new PaymentRequest([{supportedMethods: methodName}], { + total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}}, + }); + // Intentionally do not respond to the 'paymentmethodchange' event. + const response = await test_driver.bless('showing a payment sheet', () => + request.show() + ); + const complete_promise = response.complete('success'); + + assert_equals(response.details.changePaymentMethodReturned, null); + + return complete_promise; + }, 'If updateWith(details) is not run, changePaymentMethod() returns null.'); + + promise_test(async (t) => { + const request = new PaymentRequest([{supportedMethods: methodName}], { + total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}}, + }); + request.addEventListener('paymentmethodchange', (event) => { + assert_equals(event.methodName, methodName); + assert_equals(event.methodDetails.country, 'US'); + event.updateWith(Promise.reject('Error')); + }); + const response_promise = test_driver.bless( + 'showing a payment sheet', + () => request.show() + ); + + return promise_rejects_dom(t, 'AbortError', response_promise); + }, 'If updateWith(details) is rejected, abort the PaymentRequest.show().'); + + promise_test(async (t) => { + const request = new PaymentRequest([{supportedMethods: methodName}], { + total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}}, + }); + request.addEventListener('paymentmethodchange', (event) => { + assert_equals(event.methodName, methodName); + assert_equals(event.methodDetails.country, 'US'); + event.updateWith( + new Promise(() => { + throw 'Error for test'; + }) + ); + }); + const response_promise = test_driver.bless( + 'showing a payment sheet', + () => request.show() + ); + + return promise_rejects_dom(t, 'AbortError', response_promise); + }, 'If updateWith(details) throws inside of the promise, abort the PaymentRequest.show().'); + + promise_test(async (t) => { + const request = new PaymentRequest([{supportedMethods: methodName}], { + total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}}, + }); + request.addEventListener('paymentmethodchange', (event) => { + assert_equals(event.methodName, methodName); + assert_equals(event.methodDetails.country, 'US'); + event.updateWith({ + total: {label: 'Total', amount: {currency: 'GBP', value: '0.02'}}, + error: 'Error for test', + modifiers: [ + { + supportedMethods: methodName, + data: {soup: 'potato'}, + total: { + label: 'Modified total', + amount: {currency: 'EUR', value: '0.03'}, + }, + additionalDisplayItems: [ + { + label: 'Modified display item', + amount: {currency: 'INR', value: '0.06'}, + }, + ], + }, + { + supportedMethods: methodName + '2', + data: {soup: 'tomato'}, + total: { + label: 'Modified total #2', + amount: {currency: 'CHF', value: '0.07'}, + }, + additionalDisplayItems: [ + { + label: 'Modified display item #2', + amount: {currency: 'CAD', value: '0.08'}, + }, + ], + }, + ], + paymentMethodErrors: {country: 'Unsupported country'}, + displayItems: [ + { + label: 'Display item', + amount: {currency: 'CNY', value: '0.04'}, + }, + ], + shippingOptions: [ + { + label: 'Shipping option', + id: 'id', + amount: {currency: 'JPY', value: '0.05'}, + }, + ], + }); + }); + const response = await test_driver.bless('showing a payment sheet', () => + request.show() + ); + const complete_promise = response.complete('success'); + const changePaymentMethodReturned = + response.details.changePaymentMethodReturned; + + assert_equals(changePaymentMethodReturned.total.currency, 'GBP'); + assert_equals(changePaymentMethodReturned.total.value, '0.02'); + assert_equals(changePaymentMethodReturned.total.label, undefined); + assert_equals(changePaymentMethodReturned.error, 'Error for test'); + assert_equals(changePaymentMethodReturned.modifiers.length, 1); + assert_equals(changePaymentMethodReturned.displayItems, undefined); + assert_equals(changePaymentMethodReturned.shippingOptions, undefined); + assert_equals( + changePaymentMethodReturned.paymentMethodErrors.country, + 'Unsupported country' + ); + + const modifier = changePaymentMethodReturned.modifiers[0]; + + assert_equals(modifier.supportedMethods, methodName); + assert_equals(modifier.data.soup, 'potato'); + assert_equals(modifier.total.label, ''); + assert_equals(modifier.total.amount.currency, 'EUR'); + assert_equals(modifier.total.amount.value, '0.03'); + assert_equals(modifier.additionalDisplayItems, undefined); + + return complete_promise; + }, 'The changePaymentMethod() returns some details from the "paymentmethodchange" event\'s updateWith(details) call.'); + } + + registerAndActiveServiceWorker( + 'app-change-payment-method.js', + 'payment-app/', + runTests + ); +</script> diff --git a/testing/web-platform/tests/payment-handler/change-shipping-address-manual.https.html b/testing/web-platform/tests/payment-handler/change-shipping-address-manual.https.html new file mode 100644 index 0000000000..3b98d56a25 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/change-shipping-address-manual.https.html @@ -0,0 +1,161 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>Tests for PaymentRequestEvent.changeShippingAddress()</title> + +<link rel="manifest" href="/payment-handler/basic-card.json" /> +<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 src="register-and-activate-service-worker.js"></script> +<p>If the payment sheet is shown, please authorize the mock payment.</p> +<script> + const methodName = window.location.origin + '/payment-handler/payment-app/'; + function createRequest() { + return new PaymentRequest([{supportedMethods: methodName}], { + total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}}, + shippingOptions: [{ + id: 'freeShippingOption', + label: 'Free global shipping', + amount: { + currency: 'USD', + value: '0', + }, + selected: false, + }], + }, {requestShipping: true}); + } + + async function completeAppSetUp(registration) { + await registration.paymentManager.instruments.clear(); + await registration.paymentManager.instruments.set('instrument-key', { + name: 'Instrument Name', + method: methodName, + }); + await navigator.serviceWorker.ready; + await registration.paymentManager.enableDelegations(['shippingAddress']); + } + + async function runTests(registration) { + await completeAppSetUp(registration); + promise_test(async (t) => { + const request = createRequest(); + // Intentionally do not respond to the 'shippingaddresschange' event. + const response = await test_driver.bless('showing a payment sheet', () => + request.show() + ); + const complete_promise = response.complete('success'); + + assert_equals(response.details.changeShippingAddressReturned, null); + + return complete_promise; + }, 'If updateWith(details) is not run, changeShippingAddress() returns null.'); + + promise_test(async (t) => { + const request = createRequest(); + request.addEventListener('shippingaddresschange', (event) => { + assert_equals(request.shippingAddress.organization, '', 'organization should be redacted'); + assert_equals(request.shippingAddress.phone, '', 'phone should be redacted'); + assert_equals(request.shippingAddress.recipient, '', 'recipient should be redacted'); + assert_equals(request.shippingAddress.addressLine.length, 0, 'addressLine should be redacted'); + assert_equals(request.shippingAddress.city, 'Reston'); + assert_equals(request.shippingAddress.country, 'US'); + assert_equals(request.shippingAddress.postalCode, '20190'); + assert_equals(request.shippingAddress.region, 'VA'); + event.updateWith({ + total: {label: 'Total', amount: {currency: 'GBP', value: '0.02'}}, + error: 'Error for test', + modifiers: [ + { + supportedMethods: methodName, + data: {soup: 'potato'}, + total: { + label: 'Modified total', + amount: {currency: 'EUR', value: '0.03'}, + }, + additionalDisplayItems: [ + { + label: 'Modified display item', + amount: {currency: 'INR', value: '0.06'}, + }, + ], + }, + { + supportedMethods: methodName + '2', + data: {soup: 'tomato'}, + total: { + label: 'Modified total #2', + amount: {currency: 'CHF', value: '0.07'}, + }, + additionalDisplayItems: [ + { + label: 'Modified display item #2', + amount: {currency: 'CAD', value: '0.08'}, + }, + ], + }, + ], + displayItems: [ + { + label: 'Display item', + amount: {currency: 'CNY', value: '0.04'}, + }, + ], + shippingOptions: [ + { + id: 'freeShippingOption', + label: 'express global shipping', + amount: { + currency: 'USD', + value: '0', + }, + selected: true, + } + ], + shippingAddressErrors: { + country: 'US only shipping', + } + }); + }); + const response = await test_driver.bless('showing a payment sheet', () => + request.show() + ); + const complete_promise = response.complete('success'); + const changeShippingAddressReturned = + response.details.changeShippingAddressReturned; + + assert_equals(changeShippingAddressReturned.total.currency, 'GBP'); + assert_equals(changeShippingAddressReturned.total.value, '0.02'); + assert_equals(changeShippingAddressReturned.total.label, undefined); + assert_equals(changeShippingAddressReturned.error, 'Error for test'); + assert_equals(changeShippingAddressReturned.modifiers.length, 1); + assert_equals(changeShippingAddressReturned.displayItems, undefined); + assert_equals(changeShippingAddressReturned.shippingOptions.length, 1); + assert_equals(changeShippingAddressReturned.paymentMethodErrors, undefined); + assert_equals(changeShippingAddressReturned.shippingAddressErrors.country, 'US only shipping'); + + const shipping_option = changeShippingAddressReturned.shippingOptions[0]; + assert_equals(shipping_option.id, 'freeShippingOption' ); + assert_equals(shipping_option.label, 'express global shipping'); + assert_equals(shipping_option.amount.currency, 'USD'); + assert_equals(shipping_option.amount.value, '0'); + assert_true(shipping_option.selected); + + const modifier = changeShippingAddressReturned.modifiers[0]; + assert_equals(modifier.supportedMethods, methodName); + assert_equals(modifier.data.soup, 'potato'); + assert_equals(modifier.total.label, ''); + assert_equals(modifier.total.amount.currency, 'EUR'); + assert_equals(modifier.total.amount.value, '0.03'); + assert_equals(modifier.additionalDisplayItems, undefined); + + return complete_promise; + }, 'The changeShippingAddress() returns some details from the "shippingaddresschange" event\'s updateWith(details) call.'); + } + + registerAndActiveServiceWorker( + 'app-change-shipping-address.js', + 'payment-app/', + runTests + ); +</script> diff --git a/testing/web-platform/tests/payment-handler/change-shipping-option-manual.https.html b/testing/web-platform/tests/payment-handler/change-shipping-option-manual.https.html new file mode 100644 index 0000000000..2511fc5ea0 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/change-shipping-option-manual.https.html @@ -0,0 +1,160 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>Tests for PaymentRequestEvent.changeShippingOption()</title> + +<link rel="manifest" href="/payment-handler/manifest.json" /> +<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 src="register-and-activate-service-worker.js"></script> +<p>If the payment sheet is shown, please authorize the mock payment.</p> +<script> + const methodName = window.location.origin + '/payment-handler/payment-app/'; + function createRequest() { + return new PaymentRequest([{supportedMethods: methodName}], { + total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}}, + shippingOptions: [{ + id: 'freeShippingOption', + label: 'Free global shipping', + amount: { + currency: 'USD', + value: '0', + }, + selected: false, + }, + { + id: 'expressShippingOption', + label: 'express global shipping', + amount: { + currency: 'USD', + value: '0', + }, + selected: true, + }], + }, {requestShipping: true}); + } + + async function completeAppSetUp(registration) { + await registration.paymentManager.instruments.clear(); + await registration.paymentManager.instruments.set('instrument-key', { + name: 'Instrument Name', + method: methodName, + }); + await navigator.serviceWorker.ready; + await registration.paymentManager.enableDelegations(['shippingAddress']); + } + + async function runTests(registration) { + await completeAppSetUp(registration); + promise_test(async (t) => { + const request = createRequest(); + // Intentionally do not respond to the 'shippingoptionchange' event. + const response = await test_driver.bless('showing a payment sheet', () => + request.show() + ); + const complete_promise = response.complete('success'); + + assert_equals(response.details.changeShippingOptionReturned, null); + + return complete_promise; + }, 'If updateWith(details) is not run, changeShippingOption() returns null.'); + + promise_test(async (t) => { + const request = createRequest(); + request.addEventListener('shippingoptionchange', (event) => { + assert_equals(request.shippingOption, 'freeShippingOption'); + event.updateWith({ + total: {label: 'Total', amount: {currency: 'GBP', value: '0.02'}}, + error: 'Error for test', + modifiers: [ + { + supportedMethods: methodName, + data: {soup: 'potato'}, + total: { + label: 'Modified total', + amount: {currency: 'EUR', value: '0.03'}, + }, + additionalDisplayItems: [ + { + label: 'Modified display item', + amount: {currency: 'INR', value: '0.06'}, + }, + ], + }, + { + supportedMethods: methodName + '2', + data: {soup: 'tomato'}, + total: { + label: 'Modified total #2', + amount: {currency: 'CHF', value: '0.07'}, + }, + additionalDisplayItems: [ + { + label: 'Modified display item #2', + amount: {currency: 'CAD', value: '0.08'}, + }, + ], + }, + ], + displayItems: [ + { + label: 'Display item', + amount: {currency: 'CNY', value: '0.04'}, + }, + ], + shippingOptions: [ + { + id: 'freeShippingOption', + label: 'express global shipping', + amount: { + currency: 'USD', + value: '0', + }, + selected: true, + } + ], + }); + }); + const response = await test_driver.bless('showing a payment sheet', () => + request.show() + ); + const complete_promise = response.complete('success'); + const changeShippingOptionReturned = + response.details.changeShippingOptionReturned; + + assert_equals(changeShippingOptionReturned.total.currency, 'GBP'); + assert_equals(changeShippingOptionReturned.total.value, '0.02'); + assert_equals(changeShippingOptionReturned.total.label, undefined); + assert_equals(changeShippingOptionReturned.error, 'Error for test'); + assert_equals(changeShippingOptionReturned.modifiers.length, 1); + assert_equals(changeShippingOptionReturned.displayItems, undefined); + assert_equals(changeShippingOptionReturned.shippingOptions.length, 1); + assert_equals(changeShippingOptionReturned.paymentMethodErrors, undefined); + assert_equals(changeShippingOptionReturned.shippingAddressErrors, undefined); + + const shipping_option = changeShippingOptionReturned.shippingOptions[0]; + assert_equals(shipping_option.id, 'freeShippingOption' ); + assert_equals(shipping_option.label, 'express global shipping'); + assert_equals(shipping_option.amount.currency, 'USD'); + assert_equals(shipping_option.amount.value, '0'); + assert_true(shipping_option.selected); + + const modifier = changeShippingOptionReturned.modifiers[0]; + assert_equals(modifier.supportedMethods, methodName); + assert_equals(modifier.data.soup, 'potato'); + assert_equals(modifier.total.label, ''); + assert_equals(modifier.total.amount.currency, 'EUR'); + assert_equals(modifier.total.amount.value, '0.03'); + assert_equals(modifier.additionalDisplayItems, undefined); + + return complete_promise; + }, 'The changeShippingOption() returns some details from the "shippingoptionchange" event\'s updateWith(details) call.'); + } + + registerAndActiveServiceWorker( + 'app-change-shipping-option.js', + 'payment-app/', + runTests + ); +</script> diff --git a/testing/web-platform/tests/payment-handler/idlharness.https.any.js b/testing/web-platform/tests/payment-handler/idlharness.https.any.js new file mode 100644 index 0000000000..dfb0190aba --- /dev/null +++ b/testing/web-platform/tests/payment-handler/idlharness.https.any.js @@ -0,0 +1,47 @@ +// META: global=window,worker +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: script=/service-workers/service-worker/resources/test-helpers.sub.js +// META: timeout=long + +'use strict'; + +// https://w3c.github.io/payment-handler/ + +idl_test( + ['payment-handler'], + ['service-workers', 'html', 'dom'], + async (idl_array, t) => { + const isWindow = self.GLOBAL.isWindow(); + const isServiceWorker = 'ServiceWorkerGlobalScope' in self; + const hasRegistration = isServiceWorker || isWindow; + + if (hasRegistration) { + idl_array.add_objects({ + ServiceWorkerRegistration: ['registration'], + PaymentManager: ['paymentManager'], + PaymentInstruments: ['instruments'], + }); + } + if (isServiceWorker) { + idl_array.add_objects({ + ServiceWorkerGlobalScope: ['self'], + CanMakePaymentEvent: ['new CanMakePaymentEvent("type")'], + PaymentRequestEvent: ['new PaymentRequestEvent("type")'], + }) + } + + if (isWindow) { + const scope = '/service-workers/service-worker/resources/'; + const reg = await service_worker_unregister_and_register( + t, '/service-workers/service-worker/resources/empty-worker.js', scope); + self.registration = reg; + await wait_for_state(t, reg.installing, "activated"); + add_completion_callback(() => reg.unregister()); + } + if (hasRegistration) { + self.paymentManager = self.registration.paymentManager; + self.instruments = self.paymentManager.instruments; + } + } +); diff --git a/testing/web-platform/tests/payment-handler/manifest.json b/testing/web-platform/tests/payment-handler/manifest.json new file mode 100644 index 0000000000..875d74b663 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "Test Payment Handler", + "icons": [ + { + "src": "/images/rgrg-256x256.png", + "sizes": "256x256", + "type": "image/png" + } + ] +} diff --git a/testing/web-platform/tests/payment-handler/payment-app/payment.html b/testing/web-platform/tests/payment-handler/payment-app/payment.html new file mode 100644 index 0000000000..37d2452ed9 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/payment-app/payment.html @@ -0,0 +1,5 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Test Payment App</title> +<p>Account balance: $10.00</p> +<button>Authorize</button> diff --git a/testing/web-platform/tests/payment-handler/payment-instruments.https.html b/testing/web-platform/tests/payment-handler/payment-instruments.https.html new file mode 100644 index 0000000000..121c131568 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/payment-instruments.https.html @@ -0,0 +1,379 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Tests for PaymentInstruments interface</title> +<link rel="help" href="https://w3c.github.io/payment-handler/#paymentinstruments-interface"> +<link rel="manifest" href="manifest.json"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="register-and-activate-service-worker.js"></script> +<script> +function runTests(registration) { + const methodName = window.location.origin + '/payment-handler/payment-app/'; + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + await registration.paymentManager.instruments.set('instrument-key-1', { + name: 'Instrument Name 1', + }); + await registration.paymentManager.instruments.set('instrument-key-2', { + name: 'Instrument Name 2', + }); + await registration.paymentManager.instruments.delete('instrument-key-1'); + await registration.paymentManager.instruments.set('instrument-key-1', { + name: 'Instrument Name 1', + }); + const keys = await registration.paymentManager.instruments.keys(); + assert_array_equals(keys, ['instrument-key-2', 'instrument-key-1']); + }, 'Instrument keys are returned in the original insertion order'); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + await registration.paymentManager.instruments.set( + 'existing-instrument-key', + { + name: 'Instrument Name', + }, + ); + const result = await registration.paymentManager.instruments.delete( + 'existing-instrument-key', + ); + assert_true(result); + }, 'Deleting an existing instrument returns true'); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + await registration.paymentManager.instruments.set( + 'existing-instrument-key', + { + name: 'Instrument Name', + }, + ); + await registration.paymentManager.instruments.delete( + 'existing-instrument-key', + ); + const result = await registration.paymentManager.instruments.delete( + 'existing-instrument-key', + ); + assert_false(result); + }, 'Deleting an existing instrument the second time returns false'); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + const result = await registration.paymentManager.instruments.delete( + 'non-existing-instrument-key', + ); + assert_false(result); + }, 'Deleting a non-existing instrument returns false'); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + await registration.paymentManager.instruments.set( + 'existing-instrument-key', + { + name: 'Instrument Name', + icons: [ + { + src: '/images/rgrg-256x256.png', + sizes: '256x256', + type: 'image/png', + }, + ], + method: methodName, + }, + ); + const result = await registration.paymentManager.instruments.get( + 'existing-instrument-key', + ); + assert_equals(result.name, 'Instrument Name'); + }, 'Getting an existing instrument returns the instrument'); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + const result = await registration.paymentManager.instruments.get( + 'non-existing-instrument-key', + ); + assert_equals(result, undefined); + }, 'Getting a non-existing instrument returns undefined'); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + await registration.paymentManager.instruments.set( + 'existing-instrument-key', + { + name: 'Instrument Name v1', + icons: [ + {src: '/images/green-16x16.png', sizes: '16x16', type: 'image/png'}, + ], + method: methodName, + }, + ); + let result = await registration.paymentManager.instruments.get( + 'existing-instrument-key', + ); + assert_equals(result.name, 'Instrument Name v1'); + assert_equals(result.icons.length, 1); + assert_equals( + result.icons[0].src, + new URL('/images/green-16x16.png', window.location.href).href, + ); + assert_equals(result.icons[0].sizes, '16x16'); + assert_equals(result.icons[0].type, 'image/png'); + assert_equals(result.method, methodName); + assert_array_equals(result.capabilities.supportedNetworks, ['mir']); + await registration.paymentManager.instruments.set( + 'existing-instrument-key', + { + name: 'Instrument Name v2', + icons: [ + { + src: '/images/rgrg-256x256.png', + sizes: '256x256', + type: 'image/png', + }, + ], + method: methodName, + }, + ); + result = await registration.paymentManager.instruments.get( + 'existing-instrument-key', + ); + assert_equals(result.name, 'Instrument Name v2'); + assert_equals(result.icons.length, 1); + assert_equals( + result.icons[0].src, + new URL('/images/rgrg-256x256.png', window.location.href).href, + ); + assert_equals(result.icons[0].sizes, '256x256'); + assert_equals(result.icons[0].type, 'image/png'); + assert_equals(result.method, methodName); + }, 'Resetting an existing instrument updates the instrument'); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + await registration.paymentManager.instruments.set( + 'existing-instrument-key', + { + name: 'Instrument Name', + icons: [ + { + src: '/images/rgrg-256x256.png', + sizes: '256x256', + type: 'image/png', + }, + ], + method: methodName, + }, + ); + await registration.paymentManager.instruments.clear(); + const result = await registration.paymentManager.instruments.get( + 'existing-instrument-key', + ); + assert_equals(result, undefined); + }, 'Clearing the instruments'); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + const setPromise = registration.paymentManager.instruments.set( + 'instrument-key', + { + name: 'Instrument Name', + icons: [ + { + src: '/images/rgrg-256x256.png', + sizes: '256x256', + type: 'image/jif', + }, + ], + method: methodName, + }, + ); + return promise_rejects_js(t, TypeError, setPromise); + }, 'Cannot register instruments with invalid icon media type image/jif'); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + const setPromise = registration.paymentManager.instruments.set( + 'instrument-key', + { + name: 'Instrument Name', + icons: [ + { + src: '/images/rgrg-256x256.png', + sizes: '256x256', + type: 'image/pn' + 'g'.repeat(100000), + }, + ], + method: methodName, + }, + ); + return promise_rejects_js(t, TypeError, setPromise); + }, "Don't crash when registering instruments with very long icon media type image/pngggggg..."); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + return registration.paymentManager.instruments.set('instrument-key', { + name: 'Instrument Name', + icons: [ + { + src: '/images/rgrg-256x256.png', + sizes: '8'.repeat(100000) + 'x' + '8'.repeat(100000), + type: 'image/png', + }, + ], + method: methodName, + }); + }, "Don't crash when registering an instrument with a very long icon size 888...x888..."); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + return registration.paymentManager.instruments.set('instrument-key', { + name: 'Instrument Name', + icons: [ + { + src: '/images/rgrg-256x256.png', + type: 'image/png', + }, + ], + method: methodName, + }); + }, "Don't crash when 'sizes' missing from icon definition"); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + return registration.paymentManager.instruments.set('instrument-key', { + name: 'Instrument Name', + icons: [ + { + src: '/images/rgrg-256x256.png', + sizes: '256x256', + }, + ], + method: methodName, + }); + }, "Don't crash when 'type' missing from icon definition"); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + const setPromise = registration.paymentManager.instruments.set( + 'instrument-key', + { + name: 'Instrument Name', + icons: [ + { + src: '/images/rgrg-256x256.png', + sizes: '256 256', + type: 'image/png', + }, + ], + method: methodName, + }, + ); + return promise_rejects_js(t, TypeError, setPromise); + }, 'Cannot register instruments with invalid icon size "256 256" (missing "x")'); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + const setPromise = registration.paymentManager.instruments.set( + 'instrument-key', + { + name: 'Instrument Name', + icons: [ + { + src: '/images/rg\0rg-256x256.png', + sizes: '256x256', + type: 'image/png', + }, + ], + method: methodName, + }, + ); + return promise_rejects_js(t, TypeError, setPromise); + }, 'Cannot register instruments with invalid icon URL (has a null character)'); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + const setPromise = registration.paymentManager.instruments.set( + 'instrument-key', + { + name: 'Instrument Name', + icons: [ + { + src: 'http://test.example/images/rgrg-256x256.png', + sizes: '256x256', + type: 'image/png', + }, + ], + method: methodName, + }, + ); + return promise_rejects_js(t, TypeError, setPromise); + }, 'Cannot register instruments with non-existing non-https icon URL'); + + promise_test(async t => { + await registration.paymentManager.instruments.clear(); + const setPromise = registration.paymentManager.instruments.set( + 'instrument-key', + { + name: 'Instrument Name', + icons: [ + { + src: + 'http://www.chromium.org/_/rsrc/1438879449147/config/customLogo.gif', + sizes: '48x48', + type: 'image/gif', + }, + ], + method: methodName, + }, + ); + return promise_rejects_js(t, TypeError, setPromise); + }, 'Cannot register instruments with an existing non-https icon URL'); + + async function testUnusualStrings(existingKey, nonExistingKey) { + await registration.paymentManager.instruments.clear(); + await registration.paymentManager.instruments.set(existingKey, { + name: existingKey, + icons: [ + {src: '/images/rgrg-256x256.png', sizes: '256x256', type: 'image/png'}, + ], + method: existingKey, + capabilities: {aCapabilityName: existingKey}, + }); + const hasExistingInstrument = await registration.paymentManager.instruments.has( + existingKey, + ); + assert_true(hasExistingInstrument); + const hasNonExistingInstrument = await registration.paymentManager.instruments.has( + nonExistingKey, + ); + assert_false(hasNonExistingInstrument); + const existingInstrument = await registration.paymentManager.instruments.get( + existingKey, + ); + assert_equals(existingInstrument.name, existingKey); + const nonExistingInstrument = await registration.paymentManager.instruments.get( + nonExistingKey, + ); + assert_equals(nonExistingInstrument, undefined); + const deletedExistingInstrument = await registration.paymentManager.instruments.delete( + existingKey, + ); + assert_true(deletedExistingInstrument); + const deletedNonExistingInstrument = await registration.paymentManager.instruments.delete( + nonExistingKey, + ); + assert_false(deletedNonExistingInstrument); + } + + promise_test(async t => { + const length = 100000; + await testUnusualStrings('0'.repeat(length), '1'.repeat(length)); + }, "Don't crash on very long key, name, method, and capability strings."); + + promise_test(async t => { + await testUnusualStrings('foo\0bar', 'foo\0baz'); + }, "Don't crash on null characters in key, name, method, and capability strings."); +} + +registerAndActiveServiceWorker('app-simple.js', 'payment-app/', runTests); +</script> diff --git a/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.html b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.html new file mode 100644 index 0000000000..31ac8cafa7 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.html @@ -0,0 +1,14 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Test for PaymentRequestEvent Constructor</title> +<link rel="help" href="https://w3c.github.io/payment-handler/#the-paymentrequestevent"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> + +test(() => { + assert_false('PaymentRequestEvent' in window); +}, 'PaymentRequestEvent constructor must not be exposed in window'); + +</script> diff --git a/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.serviceworker.html b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.serviceworker.html new file mode 100644 index 0000000000..a64c4290e0 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.serviceworker.html @@ -0,0 +1,13 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Test for PaymentRequestEvent Constructor</title> +<link rel="help" href="https://w3c.github.io/payment-handler/#the-paymentrequestevent"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> + +service_worker_test('payment-request-event-constructor.https.serviceworker.js', + 'PaymentRequestEvent can be constructed in service worker'); + +</script> diff --git a/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.serviceworker.js b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.serviceworker.js new file mode 100644 index 0000000000..1aea7000c1 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.serviceworker.js @@ -0,0 +1,45 @@ +importScripts('/resources/testharness.js'); + +test(() => { + try { + new PaymentRequestEvent('test', undefined); + new PaymentRequestEvent('test', null); + new PaymentRequestEvent('test', {}); + } catch (err) { + assert_unreached(`Unexpected exception: ${err.message}`); + } +}, 'PaymentRequestEvent can be constucted in service worker.'); + +test(() => { + const ev = new PaymentRequestEvent('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'); +}, 'PaymentRequestEvent can be constructed with an EventInitDict, even if not trusted'); + +test(() => { + const ev = new PaymentRequestEvent('test', { + topOrigin: 'https://foo.com', + paymentRequestOrigin: 'https://bar.com', + methodData: [], + modifiers: [], + }); + assert_false(ev.isTrusted, 'constructed in script, so not be trusted'); + assert_equals(ev.topOrigin, 'https://foo.com'); + assert_equals(ev.paymentRequestOrigin, 'https://bar.com'); +}, 'PaymentRequestEvent can be constructed with a PaymentRequestEventInit, even if not trusted'); + +test(() => { + const ev = new PaymentRequestEvent('test', {}); + self.addEventListener('test', evt => { + assert_equals(ev, evt); + }); + self.dispatchEvent(ev); +}, 'PaymentRequestEvent can be dispatched, even if not trusted'); diff --git a/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.worker.js b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.worker.js new file mode 100644 index 0000000000..fdb71aa845 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/payment-request-event-constructor.https.worker.js @@ -0,0 +1,7 @@ +importScripts('/resources/testharness.js'); + +test(() => { + assert_false('PaymentRequestEvent' in self); +}, 'PaymentRequestEvent constructor must not be exposed in worker'); + +done(); diff --git a/testing/web-platform/tests/payment-handler/payment-request-event-manual.https.html b/testing/web-platform/tests/payment-handler/payment-request-event-manual.https.html new file mode 100644 index 0000000000..e595dd2160 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/payment-request-event-manual.https.html @@ -0,0 +1,88 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Tests for PaymentRequestEvent</title> +<link rel="help" href="https://w3c.github.io/payment-handler/#the-paymentrequestevent"> +<link rel="manifest" href="/payment-handler/basic-card.json"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="register-and-activate-service-worker.js"></script> +<p>When the payment sheet is shown, please authorize the mock payment.</p> +<script> +async function setInstrumentsAndRunTests(registration) { + const methodName = window.location.origin + '/payment-handler/payment-app/'; + await registration.paymentManager.instruments.clear(); + await registration.paymentManager.instruments.set('instrument-key', { + name: 'Instrument Name', + icons: [ + {src: '/images/rgrg-256x256.png', sizes: '256x256', type: 'image/png'}, + ], + method: methodName, + capabilities: {supportedNetworks: ['mir']}, + }); + runTests(); +} + +function runTests() { + promise_test(async t => { + const response = await new PaymentRequest( + [ + {supportedMethods: methodName, data: {}}, + {supportedMethods: 'interledger', data: {supportedNetworks: ['mir']}}, + ], + { + id: 'test-payment-request-identifier', + total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}}, + displayItems: [ + {label: 'Item 1', amount: {currency: 'CAD', value: '0.005'}}, + {label: 'Item 2', amount: {currency: 'EUR', value: '0.005'}}, + ], + modifiers: [ + { + supportedMethods: methodName, + data: {supportedNetworks: ['mir']}, + total: { + label: 'MIR total', + amount: {currency: 'USD', value: '0.0099'}, + }, + additionalDisplayItems: [ + {label: 'Item 3', amount: {currency: 'GBP', value: '-0.0001'}}, + ], + }, + { + supportedMethods: methodName, + data: {supportedNetworks: ['visa']}, + total: { + label: 'VISA total', + amount: {currency: 'USD', value: '0.0098'}, + }, + additionalDisplayItems: [ + {label: 'Item 4', amount: {currency: 'CNY', value: '-0.0002'}}, + ], + }, + { + supportedMethods: 'interledger', + data: {}, + total: { + label: 'Prepaid total', + amount: {currency: 'USD', value: '0.0097'}, + }, + additionalDisplayItems: [ + {label: 'Item 5', amount: {currency: 'JPY', value: '-0.0003'}}, + ], + }, + ], + }, + ).show(); + const promise = response.complete('success'); + assert_equals(response.requestId, 'test-payment-request-identifier'); + assert_equals(response.methodName, methodName); + return promise; + }, 'Can perform payment'); +} + +registerAndActiveServiceWorker( + 'app-simple.js', + 'payment-app/', + setInstrumentsAndRunTests, +); +</script> diff --git a/testing/web-platform/tests/payment-handler/register-and-activate-service-worker.js b/testing/web-platform/tests/payment-handler/register-and-activate-service-worker.js new file mode 100644 index 0000000000..fb54c5c064 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/register-and-activate-service-worker.js @@ -0,0 +1,28 @@ +async function registerAndActiveServiceWorker(script, scope, callback) { + const registration = await navigator.serviceWorker.register(script, {scope}); + const serviceWorker = + registration.installing || registration.waiting || registration.active; + if (serviceWorker) { + waitForServiceWorkerActivation(scope, callback); + return; + } + + registration.addEventListener('updatefound', event => { + waitForServiceWorkerActivation(scope, callback); + }); +} + +async function waitForServiceWorkerActivation(scope, callback) { + const registration = await navigator.serviceWorker.getRegistration(scope); + if (registration.active) { + callback(registration); + return; + } + + const serviceWorker = registration.installing || registration.waiting; + serviceWorker.addEventListener('statechange', event => { + if (event.target.state == 'activated') { + callback(registration); + } + }); +} diff --git a/testing/web-platform/tests/payment-handler/same-object-attributes.https.html b/testing/web-platform/tests/payment-handler/same-object-attributes.https.html new file mode 100644 index 0000000000..2e5dea3a4a --- /dev/null +++ b/testing/web-platform/tests/payment-handler/same-object-attributes.https.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<link rel="help" href="https://w3c.github.io/payment-handler/"> +<title>Test for [SameObject] attributes</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> + +promise_test(async t => { + const registration = await service_worker_unregister_and_register( + t, 'app-simple.js', 'payment-app/'); + await wait_for_state(t, registration.installing, 'activated'); + + assert_equals(registration.paymentManager, registration.paymentManager); + assert_equals(registration.paymentManager.instruments, registration.paymentManager.instruments); +}); + +</script> diff --git a/testing/web-platform/tests/payment-handler/supports-shipping-contact-delegation-manual.https.html b/testing/web-platform/tests/payment-handler/supports-shipping-contact-delegation-manual.https.html new file mode 100644 index 0000000000..939e542926 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/supports-shipping-contact-delegation-manual.https.html @@ -0,0 +1,88 @@ +<!DOCTYPE html> +<meta charset="utf-8" /> +<title>Tests for Delegation of shipping and contact collection to PH</title> +<link rel="manifest" href="/payment-handler/manifest.json" /> +<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 src="register-and-activate-service-worker.js"></script> +<p>If the payment sheet is shown, please authorize the mock payment.</p> +<script> + async function runTests(registration) { + const methodName = window.location.origin + '/payment-handler/payment-app/'; + await registration.paymentManager.instruments.clear(); + await registration.paymentManager.instruments.set('instrument-key', { + name: 'Instrument Name', + method: methodName, + }); + await navigator.serviceWorker.ready; + await registration.paymentManager.enableDelegations( + ['shippingAddress', 'payerName', 'payerPhone', 'payerEmail']); + + promise_test(async (t) => { + const request = new PaymentRequest([{supportedMethods: methodName}], { + total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}}, + shippingOptions: [{ + id: 'freeShippingOption', + label: 'Free global shipping', + amount: { + currency: 'USD', + value: '0', + }, + selected: true, + }], + }, {requestShipping: true}); + + const response = await test_driver.bless('showing a payment sheet', () => + request.show() + ); + const complete_promise = response.complete('success'); + + // Validate response + assert_equals('freeShippingOption', response.shippingOption); + assert_equals('Reston', response.shippingAddress.city); + assert_equals('US', response.shippingAddress.country); + assert_equals('20190', response.shippingAddress.postalCode); + assert_equals('VA', response.shippingAddress.region); + + return complete_promise; + }, 'Payment handler response should include shipping address and selected shipping option id.'); + + promise_test(async (t) => { + const request = new PaymentRequest([{ + supportedMethods: methodName + }], { + total: { + label: 'Total', + amount: { + currency: 'USD', + value: '0.01' + } + } + }, { + requestPayerName: true, + requestPayerEmail: true, + requestPayerPhone: true + }); + + const response = await test_driver.bless('showing a payment sheet', () => + request.show() + ); + const complete_promise = response.complete('success'); + + // Validate response. + assert_equals('John Smith', response.payerName); + assert_equals('smith@gmail.com', response.payerEmail); + assert_equals('+15555555555', response.payerPhone); + + return complete_promise; + }, 'Payment handler response should include payer\'s contact information.'); + } + + registerAndActiveServiceWorker( + 'app-supports-shipping-contact-delegation.js', + 'payment-app/', + runTests + ); +</script> diff --git a/testing/web-platform/tests/payment-handler/untrusted-event.https.html b/testing/web-platform/tests/payment-handler/untrusted-event.https.html new file mode 100644 index 0000000000..900ac79d0d --- /dev/null +++ b/testing/web-platform/tests/payment-handler/untrusted-event.https.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<link rel="help" href="https://w3c.github.io/payment-handler/"> +<title>Test for untrusted event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script> +<script> + +async function getResultFromSW(serviceWorkerContainer) { + return new Promise((resolve, reject) => { + serviceWorkerContainer.addEventListener('message', listener = e => { + serviceWorkerContainer.removeEventListener('message', listener); + if (e.data) { + resolve(e.data); + } else { + reject(); + } + }); + }); +} + +promise_test(async t => { + const registration = await service_worker_unregister_and_register( + t, 'untrusted-event.js', 'payment-app/'); + await wait_for_state(t, registration.installing, 'activated'); + + const controlled_window = (await with_iframe('payment-app/payment.html')).contentWindow; + + // Test for untrusted PaymentRequestEvent + { + const result = getResultFromSW(controlled_window.navigator.serviceWorker); + controlled_window.navigator.serviceWorker.controller.postMessage('paymentrequest'); + + const expected = [ + "InvalidStateError", /* respondWith */ + "InvalidStateError" /* openWindow */ + ]; + + assert_array_equals(await result, expected); + } + + // Test for untrusted CanMakePaymentEvent + { + const result = getResultFromSW(controlled_window.navigator.serviceWorker); + controlled_window.navigator.serviceWorker.controller.postMessage('canmakepayment'); + + const expected = [ + "InvalidStateError", /* respondWith */ + ]; + + assert_array_equals(await result, expected); + } +}); + +</script> diff --git a/testing/web-platform/tests/payment-handler/untrusted-event.js b/testing/web-platform/tests/payment-handler/untrusted-event.js new file mode 100644 index 0000000000..e067952cc3 --- /dev/null +++ b/testing/web-platform/tests/payment-handler/untrusted-event.js @@ -0,0 +1,59 @@ +let sender = null; + +self.addEventListener('message', e => { + sender = e.source; + + if (e.data == 'paymentrequest') { + self.dispatchEvent(new PaymentRequestEvent('paymentrequest', { + methodData: [{ + supportedMethods: 'https://example.com/pay' + }], + total: { + currency: 'USD', + value: '100' + }, + modifiers: [{ + supportedMethods: 'https://example.com/pay' + }] + })); + } else if (e.data == 'canmakepayment') { + self.dispatchEvent(new CanMakePaymentEvent('canmakepayment', { + methodData: [{ + supportedMethods: 'https://example.com/pay' + }], + modifiers: [{ + supportedMethods: 'https://example.com/pay' + }] + })); + } +}); + +self.addEventListener('paymentrequest', async e => { + const result = []; + + try { + e.respondWith({}); + } catch (exception) { + result.push(exception.name); + } + + try { + await e.openWindow('payment-app/payment.html'); + } catch (exception) { + result.push(exception.name); + } + + sender.postMessage(result); +}); + +self.addEventListener('canmakepayment', async e => { + const result = []; + + try { + e.respondWith({}); + } catch (exception) { + result.push(exception.name); + } + + sender.postMessage(result); +}); |