blob: 54c2fe46ec19edbea9dfbe4e2284903e2d71ab4a [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.vr;
import static org.chromium.chrome.browser.vr.TestFramework.POLL_TIMEOUT_LONG_MS;
import static org.chromium.chrome.browser.vr.VrTestFramework.PAGE_LOAD_TIMEOUT_S;
import static org.chromium.chrome.browser.vr.VrTestFramework.POLL_TIMEOUT_LONG_MS;
import static org.chromium.chrome.browser.vr.VrTestFramework.POLL_TIMEOUT_SHORT_MS;
import static org.chromium.chrome.test.util.ChromeRestriction.RESTRICTION_TYPE_VIEWER_DAYDREAM;
import android.graphics.PointF;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.uiautomator.UiDevice;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Restriction;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.vr.rules.ChromeTabbedActivityVrTestRule;
import org.chromium.chrome.browser.vr.rules.HeadTrackingMode;
import org.chromium.chrome.browser.vr.util.NativeUiUtils;
import org.chromium.chrome.browser.vr.util.TransitionUtils;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.content.browser.test.util.JavaScriptUtils;
import org.chromium.net.test.EmbeddedTestServer;
import java.io.File;
import java.util.concurrent.TimeoutException;
/**
* End-to-End test for capturing and comparing screen images for VR Browsering Dialogs
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.
Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE, "enable-features=VrBrowsingNativeAndroidUi"})
@Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM)
public class VrShellDialogTest {
// A long enough sleep after entering VR to ensure that the VR entry animations are complete.
private static final int VR_ENTRY_SLEEP_MS = 1000;
// A long enough sleep after triggering/interacting with a dialog to ensure that the interaction
// has propagated through the render pipeline, i.e. the result of the interaction will actually
// be visible on the screen.
// TODO(https://crbug.com/826841): Remove this in favor of notifications from the native code.
private static final int VR_DIALOG_RENDER_SLEEP_MS = 500;
private static final String TEST_IMAGE_DIR = "chrome/test/data/vr/UiCapture";
private static final File sBaseDirectory =
new File(UrlUtils.getIsolatedTestFilePath(TEST_IMAGE_DIR));
// We explicitly instantiate a rule here instead of using parameterization since this class
// only ever runs in ChromeTabbedActivity.
@Rule
public ChromeTabbedActivityVrTestRule mVrTestRule = new ChromeTabbedActivityVrTestRule();
private VrTestFramework mVrTestFramework;
private EmbeddedTestServer mServer;
@Before
public void setUp() throws Exception {
mVrTestFramework = new VrTestFramework(mVrTestRule);
// Create UiCapture image directory.
if (!sBaseDirectory.exists() && !sBaseDirectory.isDirectory()) {
Assert.assertTrue(sBaseDirectory.mkdirs());
}
}
@After
public void tearDown() throws Exception {
if (mServer != null) {
mServer.stopAndDestroyServer();
}
}
private boolean captureScreen(String filename) throws InterruptedException {
// Ensure that any UI changes that have been rendered and submitted have actually propogated
// to the screen.
NativeUiUtils.waitNumFrames(2);
// TODO(bsheedy): Make this work on Android P by drawing the view hierarchy to a bitmap.
File screenshotFile = new File(sBaseDirectory, filename + ".png");
if (screenshotFile.exists() && !screenshotFile.delete()) return false;
final UiDevice uiDevice =
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
return uiDevice.takeScreenshot(screenshotFile);
}
private void displayPermissionPrompt(String initialPage, String navigationCommand)
throws InterruptedException, TimeoutException {
// Trying to grant permissions on file:// URLs ends up hitting DCHECKS, so load from a local
// server instead.
if (mServer == null) {
mServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
}
mVrTestFramework.loadUrlAndAwaitInitialization(
mServer.getURL(VrTestFramework.getEmbeddedServerPathForHtmlTestFile(initialPage)),
PAGE_LOAD_TIMEOUT_S);
// Display the given permission prompt.
Assert.assertTrue(TransitionUtils.forceEnterVr());
TransitionUtils.waitForVrEntry(POLL_TIMEOUT_LONG_MS);
VrTestFramework.runJavaScriptOrFail(navigationCommand, POLL_TIMEOUT_SHORT_MS,
mVrTestFramework.getFirstTabWebContents());
TransitionUtils.waitForNativeUiPrompt(POLL_TIMEOUT_LONG_MS);
// There is currently no way to know whether a dialog has been drawn yet,
// so sleep long enough for it to show up.
Thread.sleep(VR_ENTRY_SLEEP_MS);
}
private void displayJavascriptDialog(String initialPage, String navigationCommand)
throws InterruptedException, TimeoutException {
mVrTestFramework.loadUrlAndAwaitInitialization(
VrTestFramework.getFileUrlForHtmlTestFile(initialPage), PAGE_LOAD_TIMEOUT_S);
// Display the JavaScript dialog.
Assert.assertTrue(TransitionUtils.forceEnterVr());
TransitionUtils.waitForVrEntry(POLL_TIMEOUT_LONG_MS);
// We can't use runJavaScriptOrFail here because JavaScript execution is blocked while a
// JS dialog is visible, so runJavaScriptOrFail will always time out.
JavaScriptUtils.executeJavaScript(
mVrTestFramework.getFirstTabWebContents(), navigationCommand);
TransitionUtils.waitForNativeUiPrompt(POLL_TIMEOUT_LONG_MS);
// There is currently no way to know whether a dialog has been drawn yet,
// so sleep long enough for it to show up.
Thread.sleep(VR_ENTRY_SLEEP_MS);
}
private void clickElement(String initialPage, int elementName)
throws InterruptedException, TimeoutException {
mVrTestFramework.loadUrlAndAwaitInitialization(
VrTestFramework.getFileUrlForHtmlTestFile(initialPage), PAGE_LOAD_TIMEOUT_S);
Assert.assertTrue(TransitionUtils.forceEnterVr());
TransitionUtils.waitForVrEntry(POLL_TIMEOUT_LONG_MS);
Thread.sleep(VR_ENTRY_SLEEP_MS);
NativeUiUtils.clickElementAndWaitForUiQuiescence(elementName, new PointF(0, 0));
// Technically not necessary, but clicking on native elements causes the laser to originate
// from the head, not the controller, which looks strange. Since the point of most of these
// tests is to verify that things look correct, better to have the laser in a normal
// position before taking screenshots.
NativeUiUtils.revertToRealControllerAndWaitForUiQuiescence();
}
/**
* Test navigate to 2D page and launch the Microphone dialog.
*/
@Test
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void testMicrophonePermissionPrompt() throws InterruptedException, TimeoutException {
// Display audio permissions prompt.
displayPermissionPrompt(
"test_navigation_2d_page", "navigator.getUserMedia({audio: true}, ()=>{}, ()=>{})");
// Capture image
Assert.assertTrue(captureScreen("MicrophonePermissionPrompt_Visible"));
NativeUiUtils.clickFallbackUiPositiveButton();
Assert.assertTrue(captureScreen("MicrophonePermissionPrompt_Granted"));
}
/**
* Test navigate to 2D page and launch the Camera dialog.
*/
@Test
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void testCameraPermissionPrompt() throws InterruptedException, TimeoutException {
// Display Camera permissions prompt.
displayPermissionPrompt(
"test_navigation_2d_page", "navigator.getUserMedia({video: true}, ()=>{}, ()=>{})");
// Capture image
Assert.assertTrue(captureScreen("CameraPermissionPrompt_Visible"));
}
/**
* Test navigate to 2D page and launch the Location dialog.
*/
@Test
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void testLocationPermissionPrompt() throws InterruptedException, TimeoutException {
// Display Location permissions prompt.
displayPermissionPrompt("test_navigation_2d_page",
"navigator.geolocation.getCurrentPosition(()=>{}, ()=>{})");
// Capture image
Assert.assertTrue(captureScreen("LocationPermissionPrompt_Visible"));
}
/**
* Test navigate to 2D page and launch the Notifications dialog.
*/
@Test
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void testNotificationPermissionPrompt() throws InterruptedException, TimeoutException {
// Display Notification permissions prompt.
displayPermissionPrompt(
"test_navigation_2d_page", "Notification.requestPermission(()=>{})");
// Capture image
Assert.assertTrue(captureScreen("NotificationPermissionPrompt_Visible"));
}
/**
* Test navigate to 2D page and launch the MIDI dialog.
*/
@Test
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void testMidiPermisionPrompt() throws InterruptedException, TimeoutException {
// Display MIDI permissions prompt.
displayPermissionPrompt(
"test_navigation_2d_page", "navigator.requestMIDIAccess({sysex: true})");
// Capture image
Assert.assertTrue(captureScreen("MidiPermissionPrompt_Visible"));
}
/**
* Test navigate to 2D page and display a JavaScript alert().
*/
@Test
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void testJavaScriptAlert() throws InterruptedException, TimeoutException {
// Display a JavaScript alert()
displayJavascriptDialog(
"test_navigation_2d_page", "alert('538 perf regressions detected')");
// Capture image
Assert.assertTrue(captureScreen("JavaScriptAlert_Visible"));
}
/**
* Test navigate to 2D page and display a JavaScript confirm();
*/
@Test
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void testJavaScriptConfirm() throws InterruptedException, TimeoutException {
// Display a JavaScript confirm()
displayJavascriptDialog(
"test_navigation_2d_page", "var c = confirm('This is a confirmation dialog')");
// Capture image
Assert.assertTrue(captureScreen("JavaScriptConfirm_Visible"));
NativeUiUtils.clickFallbackUiNegativeButton();
NativeUiUtils.revertToRealControllerAndWaitForUiQuiescence();
// Ensure the cancel button was clicked.
Assert.assertTrue("Negative button clicked",
VrTestFramework
.runJavaScriptOrFail("c", POLL_TIMEOUT_SHORT_MS,
mVrTestFramework.getFirstTabWebContents())
.equals("false"));
Assert.assertTrue(captureScreen("JavaScriptConfirm_Dismissed"));
}
/**
* Test navigate to 2D page and display a JavaScript prompt(). Then confirm that it behaves as
* it would outside of VR.
*/
@Test
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void testJavaScriptPrompt() throws InterruptedException, TimeoutException {
// Display a JavaScript prompt()
String expectedString = "Probably most likely yes";
displayJavascriptDialog("test_navigation_2d_page",
"var p = prompt('Are the Chrome controls broken?', '" + expectedString + "')");
// Capture image
Assert.assertTrue(captureScreen("JavaScriptPrompt_Visible"));
NativeUiUtils.clickFallbackUiPositiveButton();
NativeUiUtils.revertToRealControllerAndWaitForUiQuiescence();
// This JavaScript will only run once the prompt has been dismissed, and the return value
// will only be what we expect if the positive button was actually clicked (as opposed to
// canceled).
Assert.assertTrue("Positive button clicked",
VrTestFramework
.runJavaScriptOrFail("p == '" + expectedString + "'", POLL_TIMEOUT_SHORT_MS,
mVrTestFramework.getFirstTabWebContents())
.equals("true"));
Assert.assertTrue(captureScreen("JavaScriptPrompt_Dismissed"));
}
@Test
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void testKeyboardAppearsOnUrlBarClick() throws InterruptedException, TimeoutException {
clickElement("test_navigation_2d_page", UserFriendlyElementName.URL);
Assert.assertTrue(captureScreen("KeyboardAppearsOnUrlBarClick_Visible"));
}
@Test
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void testOverflowMenuAppears() throws InterruptedException, TimeoutException {
clickElement("test_navigation_2d_page", UserFriendlyElementName.OVERFLOW_MENU);
Assert.assertTrue(captureScreen("OverflowMenuAppears_Visible"));
}
@Test
@LargeTest
@HeadTrackingMode(HeadTrackingMode.SupportedMode.FROZEN)
public void testPageInfoAppearsOnSecurityTokenClick()
throws InterruptedException, TimeoutException {
clickElement("test_navigation_2d_page", UserFriendlyElementName.PAGE_INFO_BUTTON);
Assert.assertTrue(captureScreen("PageInfoAppearsOnSecurityTokenClick_Visible"));
}
}