Migrate credential-management WPTs to JS modules
Update the credential-management WPTs to use JS modules, including for
Mojo JS bindings.
This also refactors the supporting modules to isolate individual
tests from any browser-specific details.
Bug: 1004256
Change-Id: If5428dbd4f371fdc92c62da2a2378e8221c5c658
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2472201
Reviewed-by: Michael Moss <mmoss@chromium.org>
Reviewed-by: Robert Ma <robertma@chromium.org>
Commit-Queue: Ken Rockot <rockot@google.com>
Cr-Commit-Position: refs/heads/master@{#817715}
diff --git a/credential-management/otpcredential-get-basics.https.html b/credential-management/otpcredential-get-basics.https.html
index edeb42a..c0cac16 100644
--- a/credential-management/otpcredential-get-basics.https.html
+++ b/credential-management/otpcredential-get-basics.https.html
@@ -3,15 +3,12 @@
<title>Tests OTPCredential</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
-<script src="/resources/test-only-api.js"></script>
-<script src="support/otpcredential-helper.js"></script>
-<script>
-'use strict';
+<script type="module">
+import {Status, expectOTPRequest} from './support/otpcredential-helper.js';
promise_test(async t => {
- await expect(receive).andReturn(async () => {
- return {status: Status.kSuccess, otp: "ABC"};
- });
+ await expectOTPRequest().andReturn(
+ () => ({status: Status.SUCCESS, otp: "ABC"}));
let cred = await navigator.credentials.get({otp: {transport: ["sms"]}});
@@ -19,17 +16,15 @@
}, 'Basic usage');
promise_test(async t => {
- await expect(receive).andReturn(async () => {
- return {status: Status.kSuccess, otp: "ABC"};
- });
- await expect(receive).andReturn(async () => {
- return {status: Status.kSuccess, otp: "ABC2"};
- });
+ await expectOTPRequest().andReturn(
+ () => ({status: Status.SUCCESS, otp: "ABC"}));
+ await expectOTPRequest().andReturn(
+ () => ({status: Status.SUCCESS, otp: "ABC2"}));
let sms1 = navigator.credentials.get({otp: {transport: ["sms"]}});
let sms2 = navigator.credentials.get({otp: {transport: ["sms"]}});
- let cred2= await sms2;
+ let cred2 = await sms2;
let cred1 = await sms1;
assert_equals(cred1.code, "ABC");
@@ -37,21 +32,18 @@
}, 'Handle multiple requests in different order.');
promise_test(async t => {
- await expect(receive).andReturn(async () => {
- return {status: Status.kCancelled};
- });
- await expect(receive).andReturn(async () => {
- return {status: Status.kSuccess, otp: "success"};
- });
+ await expectOTPRequest().andReturn(() => ({status: Status.CANCELLED}));
+ await expectOTPRequest().andReturn(
+ () => ({status: Status.SUCCESS, otp: "success"}));
- let cancelled_sms = navigator.credentials.get({otp: {transport: ["sms"]}});
- let successful_sms = navigator.credentials.get({otp: {transport: ["sms"]}});
+ let cancelledRequest = navigator.credentials.get({otp: {transport: ["sms"]}});
+ let successfulCred =
+ await navigator.credentials.get({otp: {transport: ["sms"]}});
- let successful_cred = await successful_sms;
- assert_equals(successful_cred.code, "success");
+ assert_equals(successfulCred.code, "success");
try {
- await cancelled_sms;
+ await cancelledRequest;
assert_unreached('Expected AbortError to be thrown.');
} catch (error) {
assert_equals(error.name, "AbortError");
@@ -59,9 +51,7 @@
}, 'Handle multiple requests with success and error.');
promise_test(async t => {
- await expect(receive).andReturn(async () => {
- return {status: Status.kCancelled};
- });
+ await expectOTPRequest().andReturn(() => ({status: Status.CANCELLED}));
await promise_rejects_dom(t, 'AbortError', navigator.credentials.get(
{otp: {transport: ["sms"]}}));
diff --git a/credential-management/support/otpcredential-helper.js b/credential-management/support/otpcredential-helper.js
index 0c6ce8b..e07e9f5 100644
--- a/credential-management/support/otpcredential-helper.js
+++ b/credential-management/support/otpcredential-helper.js
@@ -1,52 +1,114 @@
-'use strict';
-
// These tests rely on the User Agent providing an implementation of
-// the sms retriever.
+// MockWebOTPService.
//
// In Chromium-based browsers this implementation is provided by a polyfill
// in order to reduce the amount of test-only code shipped to users. To enable
// these tests the browser must be run with these options:
// // --enable-blink-features=MojoJS,MojoJSTest
-const Status = {};
+import {isChromiumBased} from '/resources/test-only-api.m.js';
-async function loadChromiumResources() {
- const resources = [
- '/gen/mojo/public/mojom/base/time.mojom-lite.js',
- '/gen/third_party/blink/public/mojom/sms/webotp_service.mojom-lite.js',
- ];
-
- await loadMojoResources(resources, true);
- await loadScript('/resources/chromium/mock-sms-receiver.js');
-
- Status.kSuccess = blink.mojom.SmsStatus.kSuccess;
- Status.kTimeout = blink.mojom.SmsStatus.kTimeout;
- Status.kCancelled = blink.mojom.SmsStatus.kCancelled;
+/**
+ * This enumeration is used by WebOTP WPTs to control mock backend behavior.
+ * See MockWebOTPService below.
+ */
+export const Status = {
+ SUCCESS: 0,
+ UNHANDLED_REQUEST: 1,
+ CANCELLED: 2,
+ ABORTED: 3,
};
-async function create_sms_provider() {
- if (typeof SmsProvider === 'undefined') {
- if (isChromiumBased) {
- await loadChromiumResources();
- } else {
- throw new Error('Mojo testing interface is not available.');
- }
- }
- if (typeof SmsProvider === 'undefined') {
- throw new Error('Failed to set up SmsProvider.');
- }
- return new SmsProvider();
+/**
+ * A interface which must be implemented by browsers to support WebOTP WPTs.
+ */
+export class MockWebOTPService {
+ /**
+ * Accepts a function to be invoked in response to the next OTP request
+ * received by the mock. The (optionally async) function, when executed, must
+ * return an object with a `status` field holding one of the `Status` values
+ * defined above, and -- if successful -- an `otp` field containing a
+ * simulated OTP string.
+ *
+ * Tests will call this method directly to inject specific response behavior
+ * into the browser-specific mock implementation.
+ */
+ async handleNextOTPRequest(responseFunc) {}
}
-function receive() {
- throw new Error("expected to be overriden by tests");
+/**
+ * Returns a Promise resolving to a browser-specific MockWebOTPService subclass
+ * instance if one is available.
+ */
+async function createBrowserSpecificMockImpl() {
+ if (isChromiumBased) {
+ return await createChromiumMockImpl();
+ }
+ throw new Error('Unsupported browser.');
}
-function expect(call) {
+const asyncMock = createBrowserSpecificMockImpl();
+
+export function expectOTPRequest() {
return {
async andReturn(callback) {
- const mock = await create_sms_provider();
- mock.pushReturnValuesForTesting(call.name, callback);
+ const mock = await asyncMock;
+ mock.handleNextOTPRequest(callback);
}
}
}
+
+/**
+ * Instantiates a Chromium-specific subclass of MockWebOTPService.
+ */
+async function createChromiumMockImpl() {
+ const {SmsStatus, WebOTPService, WebOTPServiceReceiver} = await import(
+ '/gen/third_party/blink/public/mojom/sms/webotp_service.mojom.m.js');
+ const MockWebOTPServiceChromium = class extends MockWebOTPService {
+ constructor() {
+ super();
+ this.mojoReceiver_ = new WebOTPServiceReceiver(this);
+ this.interceptor_ =
+ new MojoInterfaceInterceptor(WebOTPService.$interfaceName);
+ this.interceptor_.oninterfacerequest = (e) => {
+ this.mojoReceiver_.$.bindHandle(e.handle);
+ };
+ this.interceptor_.start();
+ this.requestHandlers_ = [];
+ Object.freeze(this);
+ }
+
+ handleNextOTPRequest(responseFunc) {
+ this.requestHandlers_.push(responseFunc);
+ }
+
+ async receive() {
+ if (this.requestHandlers_.length == 0) {
+ throw new Error('Mock received unexpected OTP request.');
+ }
+
+ const responseFunc = this.requestHandlers_.shift();
+ const response = await responseFunc();
+ switch (response.status) {
+ case Status.SUCCESS:
+ if (typeof response.otp != 'string') {
+ throw new Error('Mock success results require an OTP string.');
+ }
+ return {status: SmsStatus.kSuccess, otp: response.otp};
+ case Status.UNHANDLED_REQUEST:
+ return {status: SmsStatus.kUnhandledRequest};
+ case Status.CANCELLED:
+ return {status: SmsStatus.kCancelled};
+ case Status.ABORTED:
+ return {status: SmsStatus.kAborted};
+ default:
+ throw new Error(
+ `Mock result contains unknown status: ${response.status}`);
+ }
+ }
+
+ async abort() {}
+ };
+ return new MockWebOTPServiceChromium();
+}
+
diff --git a/credential-management/support/otpcredential-iframe.html b/credential-management/support/otpcredential-iframe.html
index 83f25d5..4affc00 100644
--- a/credential-management/support/otpcredential-iframe.html
+++ b/credential-management/support/otpcredential-iframe.html
@@ -1,8 +1,6 @@
<!doctype html>
-<script src="/resources/test-only-api.js"></script>
-<script src="otpcredential-helper.js"></script>
-<script>
-'use strict';
+<script type="module">
+import {Status, expectOTPRequest} from './otpcredential-helper.js';
// Loading otpcredential-iframe.html in the test will make an OTPCredentials
// call on load, and trigger a postMessage upon completion.
@@ -13,24 +11,16 @@
// string errorType: error.name
// }
-// Intercept successful calls and return mocked value.
-(async function() {
- await expect(receive).andReturn(() => {
- return Promise.resolve({
- status: Status.kSuccess,
- otp: "ABC123",
- });
- });
-}());
-
window.onload = async () => {
- try {
- const credentials =
- await navigator.credentials.get({otp: {transport: ["sms"]}});
- window.parent.postMessage({result: "Pass", code: credentials.code}, '*');
- } catch (error) {
- window.parent.postMessage({result: "Fail", errorType: error.name}, '*');
- }
+ try {
+ await expectOTPRequest().andReturn(
+ () => ({status: Status.SUCCESS, otp: "ABC123"}));
+ const credentials =
+ await navigator.credentials.get({otp: {transport: ["sms"]}});
+ window.parent.postMessage({result: "Pass", code: credentials.code}, '*');
+ } catch (error) {
+ window.parent.postMessage({result: "Fail", errorType: error.name}, '*');
+ }
}
</script>
diff --git a/resources/chromium/mock-sms-receiver.js b/resources/chromium/mock-sms-receiver.js
deleted file mode 100644
index 903456d..0000000
--- a/resources/chromium/mock-sms-receiver.js
+++ /dev/null
@@ -1,51 +0,0 @@
-'use strict';
-
-const SmsProvider = (() => {
-
- class MockWebOTPService {
-
- constructor() {
- this.mojoReceiver_ = new blink.mojom.WebOTPServiceReceiver(this);
-
- this.interceptor_ =
- new MojoInterfaceInterceptor(blink.mojom.WebOTPService.$interfaceName);
-
- this.interceptor_.oninterfacerequest = (e) => {
- this.mojoReceiver_.$.bindHandle(e.handle);
- }
- this.interceptor_.start();
-
- this.returnValues_ = {};
- }
-
- async receive() {
- let call = this.returnValues_.receive ?
- this.returnValues_.receive.shift() : null;
- if (!call)
- return;
- return call();
- }
-
- async abort() {};
-
- pushReturnValuesForTesting(callName, value) {
- this.returnValues_[callName] = this.returnValues_[callName] || [];
- this.returnValues_[callName].push(value);
- return this;
- }
- }
-
- const mockWebOTPService = new MockWebOTPService();
-
- class SmsProviderChromium {
- constructor() {
- Object.freeze(this); // Make it immutable.
- }
-
- pushReturnValuesForTesting(callName, callback) {
- mockWebOTPService.pushReturnValuesForTesting(callName, callback);
- }
- }
-
- return SmsProviderChromium;
-})();
diff --git a/resources/test-only-api.m.js b/resources/test-only-api.m.js
new file mode 100644
index 0000000..984f635
--- /dev/null
+++ b/resources/test-only-api.m.js
@@ -0,0 +1,5 @@
+/* Whether the browser is Chromium-based with MojoJS enabled */
+export const isChromiumBased = 'MojoInterfaceInterceptor' in self;
+
+/* Whether the browser is WebKit-based with internal test-only API enabled */
+export const isWebKitBased = !isChromiumBased && 'internals' in self;
diff --git a/resources/test-only-api.m.js.headers b/resources/test-only-api.m.js.headers
new file mode 100644
index 0000000..5e8f640
--- /dev/null
+++ b/resources/test-only-api.m.js.headers
@@ -0,0 +1,2 @@
+Content-Type: text/javascript; charset=utf-8
+Cache-Control: max-age=3600