Convert existing orientation-event web tests to test_driver

The purpose of this change is to stop using the JS mocks-based
implementation and start using WebDriver and virtual sensors for
interoperability. WebDriver commands for Generic Sensor are defined in
https://www.w3.org/TR/generic-sensor/#automation, and the Device
Orientation-specific parts are defined in
https://www.w3.org/TR/orientation-event/#automation.

The TestHelper class was created to simplify tests. It takes care of
creating and removing the backing virtual sensors for each event type,
and handling permissions. It also contains some previously free
functions that now depend on virtual sensors (such as setData()).
More generic functions which do not need information saved in TestHelper
remain untouched in the same orientation-event-helpers.js file.

Care has been taken to avoid changing the values and the order of the
calls in the tests as much as possible. Notes about some cases where it
was not possible:
1. Currently it is not possible to set individual values to null because
     the parsing algorithms used by
     https://w3c.github.io/sensors/#update-virtual-sensor-reading-command
     always expect numbers. This affected null-values.https.html test.
2. "deviceorientation" and "deviceorientationabsolute" events whose
    readings don't differ from the previous one by a certain amount are
    ignored per spec (https://www.w3.org/TR/orientation-event/#significant-change-in-orientation).
     Because of this requirement second test values needed to be changed
     in add-listener-from-callback.https.html test for Device Orientation.
3. motion/add-listener-from-callback.https.html: The second listener is
   added before the first one is removed to avoid needlessly stopping
   the sensor.

Bug: 1520919
Change-Id: I7890f08357481fd62a71338b20950b534abbbe8d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5249795
Commit-Queue: Raphael Kubo Da Costa <raphael.kubo.da.costa@intel.com>
Auto-Submit: Juha J Vainio <juha.j.vainio@intel.com>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Reviewed-by: Raphael Kubo Da Costa <raphael.kubo.da.costa@intel.com>
Cr-Commit-Position: refs/heads/main@{#1258822}
diff --git a/orientation-event/device-orientation-events-of-detached-documents.https.html b/orientation-event/device-orientation-events-of-detached-documents.https.html
index c7ad5ec..f05be6d 100644
--- a/orientation-event/device-orientation-events-of-detached-documents.https.html
+++ b/orientation-event/device-orientation-events-of-detached-documents.https.html
@@ -1,5 +1,9 @@
 <!DOCTYPE html>
+<html>
+<head>
 <title>Device sensor event listeners for `window` of detached documents.</title>
+</head>
+<body>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/resources/testdriver.js"></script>
@@ -7,7 +11,7 @@
 <script src="resources/orientation-event-helpers.js"></script>
 <script>
 
-sensor_test(async t => {
+promise_test(async t => {
   const childFrame = document.createElement('iframe');
   childFrame.src = "/common/blank.html";
   document.body.append(childFrame);
@@ -18,6 +22,15 @@
   const contentWindow = childFrame.contentWindow;
   const contentDocument = childFrame.contentDocument;
 
+  await test_driver.set_permission({ name: 'accelerometer' }, 'granted', contentWindow);
+  await test_driver.set_permission({ name: 'gyroscope' }, 'granted', contentWindow);
+  await test_driver.set_permission({ name: 'magnetometer' }, 'granted', contentWindow);
+
+  assert_equals(await contentWindow.DeviceOrientationEvent.requestPermission(),
+                'granted');
+  assert_equals(await contentWindow.DeviceMotionEvent.requestPermission(),
+                'granted');
+
   document.body.remove(childFrame);
 
   assert_not_equals(contentWindow, null);
@@ -31,3 +44,5 @@
 }, 'Adding an event listener on the window of a detached document does not crash.');
 
 </script>
+</body>
+</html>
diff --git a/orientation-event/device-orientation-events-unavailable-on-insecure-origins.html b/orientation-event/device-orientation-events-unavailable-on-insecure-origins.html
index 571a388..6d2eee4 100644
--- a/orientation-event/device-orientation-events-unavailable-on-insecure-origins.html
+++ b/orientation-event/device-orientation-events-unavailable-on-insecure-origins.html
@@ -23,31 +23,37 @@
     assert_false('ondeviceorientationabsolute' in window);
   }, 'Event interfaces and event handlers are not exposed on `window`.');
 
-  sensor_test(async (t, sensorProvider) => {
-    const FAKE_ACCELERATION_DATA = [1, 2, 3];
-    const FAKE_LINEAR_ACCELERATION_DATA = [4, 5, 6];
-    const FAKE_GYROSCOPE_DATA = [7, 8, 9];
+  promise_test(async (t) => {
+    const helper = new SensorTestHelper(t, 'devicemotion');
+    await helper.initializeSensors();
+    const motionData = generateMotionData(1, 2, 3,
+                                          4, 5, 6,
+                                          7, 8, 9);
+    await helper.setData(motionData);
 
     window.ondevicemotion = t.unreached_func("devicemotion event should not be fired.");
-    setMockSensorDataForType(sensorProvider, 'Accelerometer', FAKE_ACCELERATION_DATA);
-    setMockSensorDataForType(sensorProvider, 'LinearAccelerationSensor', FAKE_LINEAR_ACCELERATION_DATA);
-    setMockSensorDataForType(sensorProvider, 'Gyroscope', FAKE_GYROSCOPE_DATA);
 
     await new Promise(r => t.step_timeout(r, 1000));
   }, 'addEventListener() for `devicemotion` does not crash but the handler never fires.');
 
-  sensor_test(async (t, sensorProvider) => {
-    const FAKE_ORIENTATION_DATA = [1.1, 2.2, 3.3];
+  promise_test(async (t) => {
+    const helper = new SensorTestHelper(t, 'deviceorientation');
+    await helper.initializeSensors();
+    const orientationData = generateOrientationData(1.1, 2.2, 3.3, false);
+    await helper.setData(orientationData);
+
     window.ondeviceorientation = t.unreached_func("deviceorientation event should not be fired.");
-    setMockSensorDataForType(sensorProvider, 'RelativeOrientationEulerAngles', FAKE_ORIENTATION_DATA);
 
     await new Promise(r => t.step_timeout(r, 1000));
   }, 'addEventListener() for `deviceorientation` does not crash but the handler never fires.');
 
-  sensor_test(async (t, sensorProvider) => {
-    const FAKE_ORIENTATION_DATA = [1.1, 2.2, 3.3];
+  promise_test(async (t) => {
+    const helper = new SensorTestHelper(t, 'deviceorientationabsolute');
+    await helper.initializeSensors();
+    const orientationData = generateOrientationData(1.1, 2.2, 3.3, true);
+    await helper.setData(orientationData);
+
     window.ondeviceorientationabsolute = t.unreached_func("deviceorientationabsolute event should not be fired.");
-    setMockSensorDataForType(sensorProvider, 'AbsoluteOrientationEulerAngles', FAKE_ORIENTATION_DATA);
 
     await new Promise(r => t.step_timeout(r, 1000));
   }, 'addEventListener() for `deviceorientationabsolute` does not crash but the handler never fires.');
diff --git a/orientation-event/motion/add-during-dispatch.https.html b/orientation-event/motion/add-during-dispatch.https.html
index 3a895b9..262715c 100644
--- a/orientation-event/motion/add-during-dispatch.https.html
+++ b/orientation-event/motion/add-during-dispatch.https.html
@@ -7,24 +7,30 @@
 <script>
 'use strict';
 
-sensor_test(async (t, sensorProvider) => {
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'devicemotion');
+  await helper.grantSensorsPermissions();
+  await helper.initializeSensors();
+
   const motionData = generateMotionData(1, 2, 3,
                                         4, 5, 6,
                                         7, 8, 9);
-  setMockMotionData(sensorProvider, motionData);
+  await helper.setData(motionData);
 
   return new Promise((resolve, reject) => {
     let result = reject;
     window.addEventListener('devicemotion', event1 => {
       // Now we are in event dispatch.
       assertEventEquals(event1, getExpectedMotionEvent(motionData));
+
       window.addEventListener('devicemotion', event2 => {
-        // Not call until the outer function returns.
+        // Not called until the outer function returns.
         assertEventEquals(event2, getExpectedMotionEvent(motionData));
         result();
-      });
-    });
-    result = resolve;
+      }, {once: true});
+
+      result = resolve;
+    }, {once: true});
   });
 }, 'Test no fire listeners added during event dispatch.');
 </script>
diff --git a/orientation-event/motion/add-listener-from-callback.https.html b/orientation-event/motion/add-listener-from-callback.https.html
index 0803d7c..1f8cca8 100644
--- a/orientation-event/motion/add-listener-from-callback.https.html
+++ b/orientation-event/motion/add-listener-from-callback.https.html
@@ -7,7 +7,11 @@
 <script>
 'use strict';
 
-sensor_test(async (t, sensorProvider) => {
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'devicemotion');
+  await helper.grantSensorsPermissions();
+  await helper.initializeSensors();
+
   const motionData = generateMotionData(1.1, 2.1, 3.1,
                                         1.2, 2.2, 3.2,
                                         1.3, 2.3, 3.3);
@@ -39,7 +43,7 @@
     };
   });
 
-  setMockMotionData(sensorProvider, motionData);
+  await helper.setData(motionData);
   window.addEventListener('devicemotion', firstListener);
   await firstPromise;
   await secondPromise;
diff --git a/orientation-event/motion/multiple-event-listeners.https.html b/orientation-event/motion/multiple-event-listeners.https.html
index 3b13d63..f94c9d1 100644
--- a/orientation-event/motion/multiple-event-listeners.https.html
+++ b/orientation-event/motion/multiple-event-listeners.https.html
@@ -7,11 +7,15 @@
 <script>
 'use strict';
 
-sensor_test(async (t, sensorProvider) => {
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'devicemotion');
+  await helper.grantSensorsPermissions();
+  await helper.initializeSensors();
+
   const motionData1 = generateMotionData(1, 2, 3,
                                          4, 5, 6,
                                          7, 8, 9);
-  setMockMotionData(sensorProvider, motionData1);
+  await helper.setData(motionData1);
   await Promise.all([
     waitForEvent(getExpectedMotionEvent(motionData1)),
     waitForEvent(getExpectedMotionEvent(motionData1))
@@ -20,7 +24,7 @@
   const motionData2 = generateMotionData(11, 12, 13,
                                          14, 15, 16,
                                          17, 18, 19);
-  setMockMotionData(sensorProvider, motionData2);
+  await helper.setData(motionData2);
   await waitForEvent(getExpectedMotionEvent(motionData2));
 }, 'Tests using multiple event handlers for the Device Motion API.');
 </script>
diff --git a/orientation-event/motion/null-values.https.html b/orientation-event/motion/null-values.https.html
index b6a2a16..f875afc 100644
--- a/orientation-event/motion/null-values.https.html
+++ b/orientation-event/motion/null-values.https.html
@@ -7,7 +7,10 @@
 <script>
 'use strict';
 
-sensor_test(async (t, sensorProvider) => {
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'devicemotion');
+  await helper.grantSensorsPermissions();
+
   const motionData1 = generateMotionData(1, 2, 3,
                                          null, null, null,
                                          null, null, null);
@@ -24,16 +27,27 @@
                                          null, null, null,
                                          null, null, null);
 
-  setMockMotionData(sensorProvider, motionData1);
+  await helper.initializeSensors({disabledSensors: ['accelerometer','gyroscope']});
+  await helper.setData(motionData1);
   await waitForEvent(getExpectedMotionEvent(motionData1));
+  // If test needs to change virtual sensor state from connected to not
+  // connected or vise versa, reset needs to be called. It removes created
+  // virtual sensors and creating them with different connection state is then
+  // possible.
+  await helper.reset();
 
-  setMockMotionData(sensorProvider, motionData2);
+  await helper.initializeSensors({disabledSensors: ['linear-acceleration','gyroscope']});
+  await helper.setData(motionData2);
   await waitForEvent(getExpectedMotionEvent(motionData2));
+  await helper.reset();
 
-  setMockMotionData(sensorProvider, motionData3);
+  await helper.initializeSensors({disabledSensors: ['accelerometer','linear-acceleration']});
+  await helper.setData(motionData3);
   await waitForEvent(getExpectedMotionEvent(motionData3));
+  await helper.reset();
 
-  setMockMotionData(sensorProvider, motionData4);
+  await helper.initializeSensors({disabledSensors: ['accelerometer','linear-acceleration','gyroscope']});
+  await helper.setData(motionData4);
   await waitForEvent(getExpectedMotionEvent(motionData4));
 }, 'Tests using null values for some or all of the event properties.');
 </script>
diff --git a/orientation-event/motion/page-visibility.https.html b/orientation-event/motion/page-visibility.https.html
index ea67131..2925af9 100644
--- a/orientation-event/motion/page-visibility.https.html
+++ b/orientation-event/motion/page-visibility.https.html
@@ -10,12 +10,16 @@
 <script>
 'use strict';
 
-sensor_test(async (t, sensorProvider) => {
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'devicemotion');
+  await helper.grantSensorsPermissions();
+  await helper.initializeSensors();
+
   const motionData = generateMotionData(0, 0, 0,
                                         0, 0, 0,
                                         0, 0, 0);
 
-  setMockMotionData(sensorProvider, motionData);
+  await helper.setData(motionData);
   const event = getExpectedMotionEvent(motionData);
   await waitForEvent(event);
 
diff --git a/orientation-event/orientation/absolute-fallback.https.html b/orientation-event/orientation/absolute-fallback.https.html
index 610b1b3..27a4307 100644
--- a/orientation-event/orientation/absolute-fallback.https.html
+++ b/orientation-event/orientation/absolute-fallback.https.html
@@ -7,13 +7,17 @@
 <script>
 'use strict';
 
-sensor_test(async (t, sensorProvider) => {
-  const orientationData = generateOrientationData(1.1, 2.2, 3.3, true);
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'deviceorientation');
+  await helper.grantSensorsPermissions();
 
   // Make the relative orientation sensor unavailable and set mock data for
   // the absolute one.
-  sensorProvider.setGetSensorShouldFail('RelativeOrientationEulerAngles', true);
-  setMockOrientationData(sensorProvider, orientationData);
-  return waitForEvent(getExpectedAbsoluteOrientationEvent(orientationData));
+  await helper.initializeSensors({enabledSensors: ['absolute-orientation'], disabledSensors: ['relative-orientation']});
+  const orientationData = generateOrientationData(1.1, 2.2, 3.3, true);
+
+  // Check sensor values when fallback is activated.
+  await helper.setData(orientationData);
+  await waitForEvent(getExpectedOrientationEvent(orientationData));
 }, 'Tests that deviceorientation falls back to using absolute orientation data if relative is unavailable.');
 </script>
diff --git a/orientation-event/orientation/add-listener-from-callback.https.html b/orientation-event/orientation/add-listener-from-callback.https.html
index 8f8cfa2..7f664ab 100644
--- a/orientation-event/orientation/add-listener-from-callback.https.html
+++ b/orientation-event/orientation/add-listener-from-callback.https.html
@@ -7,19 +7,31 @@
 <script>
 'use strict';
 
-sensor_test(async (t, sensorProvider) => {
-  const orientationData = generateOrientationData(1.1, 2.2, 3.3, false);
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'deviceorientation');
+  await helper.grantSensorsPermissions();
+  await helper.initializeSensors();
+
+  const orientationData1 = generateOrientationData(1.1, 2.2, 3.3, false);
+  const orientationData2 = generateOrientationData(11.1, 22.2, 33.3, false);
 
   let firstListener = null;
   let secondListener = null;
   let firstEventCount = 0;
   let firstPromise = new Promise(resolve => {
-    firstListener = (event) => {
+    firstListener = async (event) => {
       assert_true(event instanceof DeviceOrientationEvent, 'event is DeviceOrientationEvent');
       assert_equals(event.type, 'deviceorientation', 'event.type is devicemotion');
       assert_true(event.target instanceof Window, 'event is fired on the window object');
-      assertEventEquals(event, getExpectedOrientationEvent(orientationData));
+      assertEventEquals(event, getExpectedOrientationEvent(orientationData1));
       window.removeEventListener('deviceorientation', firstListener);
+      // Some implementations (e.g. Chromium) work without the call below
+      // because they disconnect from the virtual sensor in the
+      // removeEventListener() call above before connecting again, and in this
+      // case the same orientation data is still considered a significant
+      // change. This is an implementation detail though, so we explicitly pass
+      // different data here.
+      await helper.setData(orientationData2);
       if (++firstEventCount == 1) {
         window.addEventListener('deviceorientation', secondListener);
       }
@@ -30,14 +42,14 @@
   let secondEventCount = 0;
   let secondPromise = new Promise(resolve => {
     secondListener = (event) => {
-      assertEventEquals(event, getExpectedOrientationEvent(orientationData));
+      assertEventEquals(event, getExpectedOrientationEvent(orientationData2));
       window.removeEventListener('deviceorientation', secondListener);
       ++secondEventCount;
       resolve(event);
     };
   });
 
-  setMockOrientationData(sensorProvider, orientationData);
+  await helper.setData(orientationData1);
   window.addEventListener('deviceorientation', firstListener);
   await firstPromise;
   await secondPromise;
diff --git a/orientation-event/orientation/basic-operation-absolute.https.html b/orientation-event/orientation/basic-operation-absolute.https.html
index 61fd218..bebd69b 100644
--- a/orientation-event/orientation/basic-operation-absolute.https.html
+++ b/orientation-event/orientation/basic-operation-absolute.https.html
@@ -7,18 +7,24 @@
 <script>
 'use strict';
 
-sensor_test(async (t, sensorProvider) => {
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'deviceorientationabsolute');
+  await helper.grantSensorsPermissions();
+  await helper.initializeSensors();
+
   const orientationData = generateOrientationData(1.1, 2.2, 3.3, true);
-  setMockOrientationData(sensorProvider, orientationData);
-  return waitForEvent(getExpectedAbsoluteOrientationEvent(orientationData));
+  await helper.setData(orientationData);
+  await waitForEvent(getExpectedAbsoluteOrientationEvent(orientationData));
 }, 'Tests basic operation of deviceorientationabsolute event using mock data.');
 
-sensor_test(async (t, sensorProvider) => {
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'deviceorientationabsolute');
+  await helper.grantSensorsPermissions();
+  await helper.initializeSensors({disabledSensors: ['absolute-orientation']});
+
   const orientationData = generateOrientationData(null, null, null, true);
   const watcher = new EventWatcher(t, window, ['deviceorientationabsolute']);
 
-  // Make the absolute orientation sensor unavailable
-  sensorProvider.setGetSensorShouldFail('AbsoluteOrientationEulerAngles', true);
   const event = await watcher.wait_for('deviceorientationabsolute');
   assert_equals(event.type, 'deviceorientationabsolute', 'type is set to \"deviceorientationabsolute\"');
   assert_true(event instanceof DeviceOrientationEvent, 'event is DeviceOrientationEvent');
diff --git a/orientation-event/orientation/basic-operation.https.html b/orientation-event/orientation/basic-operation.https.html
index 45fe380..c4d26e3 100644
--- a/orientation-event/orientation/basic-operation.https.html
+++ b/orientation-event/orientation/basic-operation.https.html
@@ -7,19 +7,24 @@
 <script>
 'use strict';
 
-sensor_test(async (t, sensorProvider) => {
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'deviceorientation');
+  await helper.grantSensorsPermissions();
+  await helper.initializeSensors();
+
   const orientationData = generateOrientationData(1.1, 2.2, 3.3, false);
-  setMockOrientationData(sensorProvider, orientationData);
-  return waitForEvent(getExpectedOrientationEvent(orientationData));
+  await helper.setData(orientationData);
+  await waitForEvent(getExpectedOrientationEvent(orientationData));
 }, 'Tests basic operation of deviceorientation event using mock data.');
 
-sensor_test(async (t, sensorProvider) => {
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'deviceorientation');
+  await helper.grantSensorsPermissions();
+  await helper.initializeSensors({disabledSensors: ['relative-orientation']});
+
   const orientationData = generateOrientationData(null, null, null, false);
   const watcher = new EventWatcher(t, window, ['deviceorientation']);
 
-  // Make the orientation sensor unavailable
-  sensorProvider.setGetSensorShouldFail('AbsoluteOrientationEulerAngles', true);
-  sensorProvider.setGetSensorShouldFail('RelativeOrientationEulerAngles', true);
   const event = await watcher.wait_for('deviceorientation');
   assert_equals(event.type, 'deviceorientation', 'type is set to \"deviceorientation\"');
   assert_true(event instanceof DeviceOrientationEvent, 'event is DeviceOrientationEvent');
diff --git a/orientation-event/orientation/multiple-event-listeners.https.html b/orientation-event/orientation/multiple-event-listeners.https.html
index 473b7f8..a32f9f3 100644
--- a/orientation-event/orientation/multiple-event-listeners.https.html
+++ b/orientation-event/orientation/multiple-event-listeners.https.html
@@ -7,16 +7,21 @@
 <script>
 'use strict';
 
-sensor_test(async (t, sensorProvider) => {
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'deviceorientation');
+  await helper.grantSensorsPermissions();
+  await helper.initializeSensors();
+
   const orientationData1 = generateOrientationData(1, 2, 3, false);
-  setMockOrientationData(sensorProvider, orientationData1);
+  await helper.setData(orientationData1);
+
   await Promise.all([
     waitForEvent(getExpectedOrientationEvent(orientationData1)),
     waitForEvent(getExpectedOrientationEvent(orientationData1))
   ]);
 
   const orientationData2 = generateOrientationData(11, 12, 13, false);
-  setMockOrientationData(sensorProvider, orientationData2);
+  await helper.setData(orientationData2);
   await waitForEvent(getExpectedOrientationEvent(orientationData2));
 }, 'Tests using multiple event handlers for the Device Orientation API.');
 </script>
diff --git a/orientation-event/orientation/no-synchronous-events.https.html b/orientation-event/orientation/no-synchronous-events.https.html
index e5d7621..97dccfb 100644
--- a/orientation-event/orientation/no-synchronous-events.https.html
+++ b/orientation-event/orientation/no-synchronous-events.https.html
@@ -7,16 +7,13 @@
 <script>
 'use strict';
 
-sensor_test(async (t, sensorProvider) => {
-  const orientationData = generateOrientationData(1.1, 2.2, 3.3, false);
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'deviceorientation');
+  await helper.grantSensorsPermissions();
+  await helper.initializeSensors();
 
-  let setMockDataPromise = setMockOrientationData(sensorProvider, orientationData);
-  // Add an empty listener to make sure the event pump is running and the mock
-  // sensor is created and configured. If the pump and fake sensor weren't set
-  // up ahead of time, then the fact that we get an asynchronous event could be
-  // due to the asynchronous set up process.
-  window.addEventListener('deviceorientation', event => {});
-  await setMockDataPromise;
+  const orientationData = generateOrientationData(1.1, 2.2, 3.3, false);
+  await helper.setData(orientationData);
 
   return new Promise((resolve, reject) => {
     let result = reject;
diff --git a/orientation-event/orientation/null-values.https.html b/orientation-event/orientation/null-values.https.html
index f9e4aa6..c54d73d 100644
--- a/orientation-event/orientation/null-values.https.html
+++ b/orientation-event/orientation/null-values.https.html
@@ -7,24 +7,21 @@
 <script>
 'use strict';
 
-sensor_test(async (t, sensorProvider) => {
-  const orientationData1 = generateOrientationData(1.1, null, null, false);
-  const orientationData2 = generateOrientationData(null, 2.2, null, false);
-  const orientationData3 = generateOrientationData(null, null, 3.3, false);
-  // The all null event is last because DeviceSingleWindowEventController
-  // will stop updating the sensor when it sees a null event.
-  const orientationData4 = generateOrientationData(null, null, null, false);
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'deviceorientation');
+  await helper.grantSensorsPermissions();
+  await helper.initializeSensors({disabledSensors: ['absolute-orientation', 'relative-orientation']});
 
-  setMockOrientationData(sensorProvider, orientationData1);
-  await waitForEvent(getExpectedOrientationEvent(orientationData1));
+  const orientationData1 = generateOrientationData(1.1, 2.2, 3.3, false);
+  // Currently it is not possible to set individual values to null because the
+  // parsing algorithms used by
+  // https://w3c.github.io/sensors/#update-virtual-sensor-reading-command
+  // always expect numbers.
+  const orientationData2 = generateOrientationData(null, null, null, false);
 
-  setMockOrientationData(sensorProvider, orientationData2);
+  // An example how setting relative-orientation sensor as disabled will output
+  // null values. Even if we try to set non null values to sensor.
+  await helper.setData(orientationData1);
   await waitForEvent(getExpectedOrientationEvent(orientationData2));
-
-  setMockOrientationData(sensorProvider, orientationData3);
-  await waitForEvent(getExpectedOrientationEvent(orientationData3));
-
-  setMockOrientationData(sensorProvider, orientationData4);
-  await waitForEvent(getExpectedOrientationEvent(orientationData4));
 }, 'Tests using null values for some of the event properties.');
 </script>
diff --git a/orientation-event/orientation/page-visibility.https.html b/orientation-event/orientation/page-visibility.https.html
index 37ca368..bcb4bee 100644
--- a/orientation-event/orientation/page-visibility.https.html
+++ b/orientation-event/orientation/page-visibility.https.html
@@ -10,10 +10,14 @@
 <script>
 'use strict';
 
-sensor_test(async (t, sensorProvider) => {
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'deviceorientation');
+  await helper.grantSensorsPermissions();
+  await helper.initializeSensors();
+
   const orientationData = generateOrientationData(1, 2, 3, false);
 
-  setMockOrientationData(sensorProvider, orientationData);
+  await helper.setData(orientationData);
   const event = getExpectedOrientationEvent(orientationData);
   await waitForEvent(event);
 
diff --git a/orientation-event/orientation/updates.https.html b/orientation-event/orientation/updates.https.html
index c84588d..fe18115 100644
--- a/orientation-event/orientation/updates.https.html
+++ b/orientation-event/orientation/updates.https.html
@@ -7,13 +7,31 @@
 <script>
 'use strict';
 
-sensor_test(async (t, sensorProvider) => {
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'deviceorientation');
+  await helper.grantSensorsPermissions();
+  await helper.initializeSensors();
+
   const orientationData1 = generateOrientationData(1.1, 2.2, 3.3, false);
-  setMockOrientationData(sensorProvider, orientationData1);
+  await helper.setData(orientationData1);
   await waitForEvent(getExpectedOrientationEvent(orientationData1));
 
   const orientationData2 = generateOrientationData(11.1, 22.2, 33.3, false);
-  setMockOrientationData(sensorProvider, orientationData2);
+  await helper.setData(orientationData2);
   await waitForEvent(getExpectedOrientationEvent(orientationData2));
-}, 'Tests that updates to the orientation causes new events to fire.');
+}, 'Tests that updates to the relative orientation causes new events to fire.');
+
+promise_test(async (t) => {
+  const helper = new SensorTestHelper(t, 'deviceorientationabsolute');
+  await helper.grantSensorsPermissions();
+  await helper.initializeSensors();
+
+  const orientationData1 = generateOrientationData(1.1, 2.2, 3.3, true);
+  await helper.setData(orientationData1);
+  await waitForEvent(getExpectedAbsoluteOrientationEvent(orientationData1));
+
+  const orientationData2 = generateOrientationData(11.1, 22.2, 33.3, true);
+  await helper.setData(orientationData2);
+  await waitForEvent(getExpectedAbsoluteOrientationEvent(orientationData2));
+}, 'Tests that updates to the absolute orientation causes new events to fire.');
 </script>
diff --git a/orientation-event/resources/orientation-event-helpers.js b/orientation-event/resources/orientation-event-helpers.js
index d9ee728..01e91c6 100644
--- a/orientation-event/resources/orientation-event-helpers.js
+++ b/orientation-event/resources/orientation-event-helpers.js
@@ -1,135 +1,220 @@
 'use strict';
 
-// These tests rely on the User Agent providing an implementation of
-// platform sensor backends.
+// @class SensorTestHelper
 //
-// 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:
+// SensorTestHelper is a helper utilities for orientation event tests.
 //
-//   --enable-blink-features=MojoJS,MojoJSTest
-async function loadChromiumResources() {
-  await import('/resources/chromium/generic_sensor_mocks.js');
-}
+// Usage example with device orientation:
+//   const helper = new SensorTestHelper(t, 'deviceorientation');
+//   await helper.grantSensorsPermissions();
+//   await helper.initializeSensors();
+//   const generatedData = generateOrientationData(1, 2, 3, false);
+//   await helper.setData(generatedData);
+//   await waitForEvent(getExpectedOrientationEvent(generatedData));
+class SensorTestHelper {
+  #eventName;
+  #sensorsEnabledByDefault;
+  #enabledSensors;
+  #disabledSensors;
+  #testObject;
 
-async function initialize_generic_sensor_tests() {
-  if (typeof GenericSensorTest === 'undefined') {
-    const script = document.createElement('script');
-    script.src = '/resources/test-only-api.js';
-    script.async = false;
-    const p = new Promise((resolve, reject) => {
-      script.onload = () => { resolve(); };
-      script.onerror = e => { reject(e); };
-    })
-    document.head.appendChild(script);
-    await p;
+  // @param {object} t - A testharness.js subtest instance.
+  // @param {string} eventName - A name of event. Accepted values are
+  //                             devicemotion, deviceorientation or
+  //                             deviceorientationabsolute.
+  constructor(t, eventName) {
+    this.#eventName = eventName;
+    this.#testObject = t;
+    this.#testObject.add_cleanup(() => this.reset());
 
-    if (isChromiumBased) {
-      await loadChromiumResources();
+    switch (this.#eventName) {
+      case 'devicemotion':
+        this.#sensorsEnabledByDefault =
+            new Set(['accelerometer', 'gyroscope', 'linear-acceleration']);
+        break;
+      case 'deviceorientation':
+        this.#sensorsEnabledByDefault = new Set(['relative-orientation']);
+        break;
+      case 'deviceorientationabsolute':
+        this.#sensorsEnabledByDefault = new Set(['absolute-orientation']);
+        break;
+      default:
+        throw new Error(`Invalid event name ${this.#eventName}`);
     }
   }
-  assert_implements(GenericSensorTest, 'GenericSensorTest is unavailable.');
-  let sensorTest = new GenericSensorTest();
-  await sensorTest.initialize();
-  return sensorTest;
-}
 
-function sensor_test(func, name, properties) {
-  promise_test(async (t) => {
-    t.add_cleanup(() => {
-      if (sensorTest)
-        return sensorTest.reset();
+  // Creates virtual sensors that will be used in tests.
+  //
+  // This function must be called before event listeners are added or calls
+  // to setData() or waitForEvent() are made.
+  //
+  // The |options| parameter is an object that accepts the following entries:
+  // - enabledSensors: A list of virtual sensor names that will be created
+  //                   instead of the default ones for a given event type.
+  // - disabledSensors: A list of virtual sensor names that will be created
+  //                    in a disabled state, so that creating a sensor of
+  //                    a given type is guaranteed to fail.
+  // An Error is thrown if the same name is passed to both options.
+  //
+  // A default list of virtual sensors based on the |eventName| parameter passed
+  // to the constructor is used if |options| is not specified.
+  //
+  // Usage examples
+  // Use default sensors for the given event type:
+  //   await helper.initializeSensors()
+  // Enable specific sensors:
+  //   await helper.initializeSensors({
+  //     enabledSensors: ['accelerometer', 'gyroscope']
+  //   })
+  // Disable some sensors, make some report as not available:
+  //   await helper.initializeSensors({
+  //     disabledSensors: ['gyroscope']
+  //   })
+  // Enable some sensors, make some report as not available:
+  //   await helper.initializeSensors({
+  //     enabledSensors: ['accelerometer'],
+  //     disabledSensors: ['gyroscope']
+  //   })
+  async initializeSensors(options = {}) {
+    this.#disabledSensors = new Set(options.disabledSensors || []);
+    // Check that a sensor name is not in both |options.enabledSensors| and
+    // |options.disabledSensors|.
+    for (const sensor of (options.enabledSensors || [])) {
+      if (this.#disabledSensors.has(sensor)) {
+        throw new Error(`${sensor} can be defined only as enabledSensors or disabledSensors`);
+      }
+    }
+
+    this.#enabledSensors = new Set(options.enabledSensors || this.#sensorsEnabledByDefault);
+    // Remove sensors from enabledSensors that are in disabledSensors
+    for (const sensor of this.#disabledSensors) {
+      this.#enabledSensors.delete(sensor);
+    }
+
+    const createVirtualSensorPromises = [];
+    for (const sensor of this.#enabledSensors) {
+      createVirtualSensorPromises.push(
+          test_driver.create_virtual_sensor(sensor));
+    }
+    for (const sensor of this.#disabledSensors) {
+      createVirtualSensorPromises.push(
+          test_driver.create_virtual_sensor(sensor, {connected: false}));
+    }
+    await Promise.all(createVirtualSensorPromises);
+  }
+
+  // Updates virtual sensor with given data.
+  // @param {object} data - Generated data by generateMotionData or
+  //                        generateOrientationData which is passed to
+  //                        test_driver.update_virtual_sensor().
+  async setData(data) {
+    // WebDriver expects numbers for all values in the readings it receives. We
+    // convert null to zero here, but any other numeric value would work, as it
+    // is the presence of one or more sensors in initializeSensors()'
+    // options.disabledSensors that cause null to be reported in one or more
+    // event attributes.
+    const nullToZero = x => (x === null ? 0 : x);
+    if (this.#eventName === 'devicemotion') {
+      const degToRad = Math.PI / 180;
+      await Promise.all([
+        test_driver.update_virtual_sensor('accelerometer', {
+          'x': nullToZero(data.accelerationIncludingGravityX),
+          'y': nullToZero(data.accelerationIncludingGravityY),
+          'z': nullToZero(data.accelerationIncludingGravityZ),
+        }),
+        test_driver.update_virtual_sensor('linear-acceleration', {
+          'x': nullToZero(data.accelerationX),
+          'y': nullToZero(data.accelerationY),
+          'z': nullToZero(data.accelerationZ),
+        }),
+        test_driver.update_virtual_sensor('gyroscope', {
+          'x': nullToZero(data.rotationRateAlpha) * degToRad,
+          'y': nullToZero(data.rotationRateBeta) * degToRad,
+          'z': nullToZero(data.rotationRateGamma) * degToRad,
+        }),
+      ]);
+    } else {
+      const sensorType =
+          data.absolute ? 'absolute-orientation' : 'relative-orientation';
+      await test_driver.update_virtual_sensor(sensorType, {
+        alpha: nullToZero(data.alpha),
+        beta: nullToZero(data.beta),
+        gamma: nullToZero(data.gamma),
+      });
+    }
+  }
+
+  // Grants permissions to sensors. Depending on |eventName|, requests
+  // permission to use either the DeviceMotionEvent or the
+  // DeviceOrientationEvent API.
+  async grantSensorsPermissions() {
+    // Required by all event types.
+    await test_driver.set_permission({name: 'accelerometer'}, 'granted');
+    await test_driver.set_permission({name: 'gyroscope'}, 'granted');
+    if (this.#eventName == 'deviceorientationabsolute') {
+      await test_driver.set_permission({name: 'magnetometer'}, 'granted');
+    }
+
+    const interfaceName = this.#eventName == 'devicemotion' ?
+        DeviceMotionEvent :
+        DeviceOrientationEvent;
+    await test_driver.bless('enable user activation', async () => {
+      const permission = await interfaceName.requestPermission();
+      assert_equals(permission, 'granted');
     });
+  }
 
-    let sensorTest = await initialize_generic_sensor_tests();
-    return func(t, sensorTest.getSensorProvider());
-  }, name, properties);
+  // Resets SensorTestHelper to default state. Removes all created virtual
+  // sensors.
+  async reset() {
+    const createdVirtualSensors =
+      new Set([...this.#enabledSensors, ...this.#disabledSensors]);
+
+    const sensorRemovalPromises = [];
+    for (const sensor of createdVirtualSensors) {
+      sensorRemovalPromises.push(test_driver.remove_virtual_sensor(sensor));
+    }
+    await Promise.all(sensorRemovalPromises);
+  }
 }
 
-// If two doubles differ by less than this amount, we can consider them
-// to be effectively equal.
-const EPSILON = 1e-8;
-
-function generateMotionData(accelerationX, accelerationY, accelerationZ,
-                            accelerationIncludingGravityX,
-                            accelerationIncludingGravityY,
-                            accelerationIncludingGravityZ,
-                            rotationRateAlpha, rotationRateBeta, rotationRateGamma,
-                            interval = 16) {
-  const motionData = {accelerationX: accelerationX,
-                    accelerationY: accelerationY,
-                    accelerationZ: accelerationZ,
-                    accelerationIncludingGravityX: accelerationIncludingGravityX,
-                    accelerationIncludingGravityY: accelerationIncludingGravityY,
-                    accelerationIncludingGravityZ: accelerationIncludingGravityZ,
-                    rotationRateAlpha: rotationRateAlpha,
-                    rotationRateBeta: rotationRateBeta,
-                    rotationRateGamma: rotationRateGamma,
-                    interval: interval};
+function generateMotionData(
+    accelerationX, accelerationY, accelerationZ, accelerationIncludingGravityX,
+    accelerationIncludingGravityY, accelerationIncludingGravityZ,
+    rotationRateAlpha, rotationRateBeta, rotationRateGamma, interval = 16) {
+  const motionData = {
+    accelerationX: accelerationX,
+    accelerationY: accelerationY,
+    accelerationZ: accelerationZ,
+    accelerationIncludingGravityX: accelerationIncludingGravityX,
+    accelerationIncludingGravityY: accelerationIncludingGravityY,
+    accelerationIncludingGravityZ: accelerationIncludingGravityZ,
+    rotationRateAlpha: rotationRateAlpha,
+    rotationRateBeta: rotationRateBeta,
+    rotationRateGamma: rotationRateGamma,
+    interval: interval
+  };
   return motionData;
 }
 
 function generateOrientationData(alpha, beta, gamma, absolute) {
-  const orientationData = {alpha: alpha,
-                         beta: beta,
-                         gamma: gamma,
-                         absolute: absolute};
+  const orientationData =
+      {alpha: alpha, beta: beta, gamma: gamma, absolute: absolute};
   return orientationData;
 }
 
-async function setMockSensorDataForType(sensorProvider, sensorType, mockDataArray) {
-  const createdSensor = await sensorProvider.getCreatedSensor(sensorType);
-  // We call setSensorReadingAndUpdateSharedBuffer() rather than
-  // setSensorReading() to accommodate Blink's Device Orientation
-  // implementation, which uses its own timer to read the sensor's shared
-  // memory buffer rather than relying on SensorReadingChanged(). This timer
-  // may fire out of sync with the JS timer in MockSensor.startReading(), so
-  // the former might read the shared memory buffer before the latter has
-  // updated |this.buffer_|. We thus immediately update the buffer here
-  // (without consuming data from the ring buffer).
-  return createdSensor.setSensorReadingImmediately([mockDataArray]);
-}
-
-// Device[Orientation|Motion]EventPump treat NaN as a missing value.
-let nullToNan = x => (x === null ? NaN : x);
-
-function setMockMotionData(sensorProvider, motionData) {
-  const degToRad = Math.PI / 180;
-  return Promise.all([
-      setMockSensorDataForType(sensorProvider, "Accelerometer", [
-          nullToNan(motionData.accelerationIncludingGravityX),
-          nullToNan(motionData.accelerationIncludingGravityY),
-          nullToNan(motionData.accelerationIncludingGravityZ),
-      ]),
-      setMockSensorDataForType(sensorProvider, "LinearAccelerationSensor", [
-          nullToNan(motionData.accelerationX),
-          nullToNan(motionData.accelerationY),
-          nullToNan(motionData.accelerationZ),
-      ]),
-      setMockSensorDataForType(sensorProvider, "Gyroscope", [
-          nullToNan(motionData.rotationRateAlpha) * degToRad,
-          nullToNan(motionData.rotationRateBeta) * degToRad,
-          nullToNan(motionData.rotationRateGamma) * degToRad,
-      ]),
-  ]);
-}
-
-function setMockOrientationData(sensorProvider, orientationData) {
-  let sensorType = orientationData.absolute
-      ? "AbsoluteOrientationEulerAngles" : "RelativeOrientationEulerAngles";
-  return setMockSensorDataForType(sensorProvider, sensorType, [
-      nullToNan(orientationData.beta),
-      nullToNan(orientationData.gamma),
-      nullToNan(orientationData.alpha),
-  ]);
-}
-
 function assertEventEquals(actualEvent, expectedEvent) {
+  // If two doubles differ by less than this amount, we can consider them
+  // to be effectively equal.
+  const EPSILON = 1e-8;
+
   for (let key1 of Object.keys(Object.getPrototypeOf(expectedEvent))) {
-    if (typeof expectedEvent[key1] === "object" && expectedEvent[key1] !== null) {
+    if (typeof expectedEvent[key1] === 'object' &&
+        expectedEvent[key1] !== null) {
       assertEventEquals(actualEvent[key1], expectedEvent[key1]);
-    } else if (typeof expectedEvent[key1] === "number") {
-      assert_approx_equals(actualEvent[key1], expectedEvent[key1], EPSILON, key1);
+    } else if (typeof expectedEvent[key1] === 'number') {
+      assert_approx_equals(
+          actualEvent[key1], expectedEvent[key1], EPSILON, key1);
     } else {
       assert_equals(actualEvent[key1], expectedEvent[key1], key1);
     }
@@ -184,6 +269,6 @@
       } catch (e) {
         reject(e);
       }
-    }, { once: true });
+    }, {once: true});
   });
 }