chromium/android_webview/tools/system_webview_shell/layout_tests/src/org/chromium/webview_shell/test/WebPlatformTestsActivityTest.java

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.webview_shell.test;

import static org.chromium.base.test.util.ScalableTimeout.scaleTimeout;

import android.os.Handler;
import android.webkit.WebView;

import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;

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.BaseActivityTestRule;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.webview_shell.WebPlatformTestsActivity;

import java.util.ArrayList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/** Tests to ensure that system webview shell can handle WPT tests correctly. */
@RunWith(BaseJUnit4ClassRunner.class)
public class WebPlatformTestsActivityTest {
    private static final int CHILD_LAYOUT_ADDED = 1;
    private static final int CHILD_LAYOUT_REMOVED = 2;

    private static final long TEST_TIMEOUT_IN_SECONDS = scaleTimeout(5);
    private static final long ON_CREATE_WINDOW_DELAY_MS = 100;

    private static final String OPEN_CLOSE_TEST_WINDOW_SCRIPT =
            "<html><head><script>function ensure_test_window() {"
                    + "  if (!this.test_window || this.test_window.location === null) {"
                    + "    this.test_window = window.open('about:blank', 800, 600);"
                    // Adding delay due to https://crbug.com/1002727
                    + "    setTimeout(function() { this.test_window.close(); }, "
                    + ON_CREATE_WINDOW_DELAY_MS
                    + ");"
                    + "  }"
                    + "};"
                    + "ensure_test_window();"
                    + "</script></head><body>TestRunner Window</body></html>";

    private static final String MULTIPLE_OPEN_CLOSE_TEST_WINDOW_SCRIPT =
            "<html><head><script>function ensure_test_window() {"
                    + "  if (!this.test_window || this.test_window.location === null) {"
                    + "    this.test_window = window.open('about:blank', 800, 600);"
                    + "  }"
                    + "};"
                    + "ensure_test_window();"
                    + "</script></head><body>TestRunner Window</body></html>";

    private WebPlatformTestsActivity mTestActivity;

    @Rule
    public BaseActivityTestRule<WebPlatformTestsActivity> mActivityTestRule =
            new BaseActivityTestRule<>(WebPlatformTestsActivity.class);

    @Before
    public void setUp() {
        mActivityTestRule.launchActivity(null);
        mTestActivity = mActivityTestRule.getActivity();
    }

    @Test
    @MediumTest
    @DisabledTest(message = "https://crbug.com/1096214")
    public void testOpenCloseWindow() throws Exception {
        final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();

        InstrumentationRegistry.getInstrumentation()
                .runOnMainSync(
                        () -> {
                            mTestActivity.setTestCallback(
                                    new WebPlatformTestsActivity.TestCallback() {
                                        @Override
                                        public void onChildLayoutAdded(WebView webView) {
                                            queue.add(CHILD_LAYOUT_ADDED);
                                        }

                                        @Override
                                        public void onChildLayoutRemoved() {
                                            queue.add(CHILD_LAYOUT_REMOVED);
                                        }
                                    });
                            WebView webView = mTestActivity.getTestRunnerWebView();
                            webView.loadDataWithBaseURL(
                                    "https://some.domain.test/",
                                    OPEN_CLOSE_TEST_WINDOW_SCRIPT,
                                    "text/html",
                                    null,
                                    null);
                        });
        assertNextElementFromQueue("Child window should be added.", CHILD_LAYOUT_ADDED, queue);
        assertNextElementFromQueue("Child window should be removed.", CHILD_LAYOUT_REMOVED, queue);
    }

    @Test
    @MediumTest
    public void testNestedOpensAndCloses() throws Exception {
        final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
        final int depthToTest = 3;
        ArrayList<WebView> webViewList = new ArrayList<>();

        var testCallback =
                new WebPlatformTestsActivity.TestCallback() {
                    private int mDepthCounter = 1; // one popup window is already opened by script.

                    @Override
                    public void onChildLayoutAdded(WebView webView) {
                        // This logic should be moved to onPageFinished() once
                        // https://crbug.com/1002727 is fixed.
                        if (mDepthCounter < depthToTest) {
                            // Open another popup. Call evaluateJavascript later
                            // since loading may not have completed. This
                            // delay is also needed in opening new popup
                            // window due to https://crbug.com/1002727.
                            String js = "window.open('about:blank', '_blank');";
                            new Handler()
                                    .postDelayed(
                                            () -> webView.evaluateJavascript(js, null),
                                            ON_CREATE_WINDOW_DELAY_MS);
                            mDepthCounter++;
                        }
                        webViewList.add(webView);
                        queue.add(CHILD_LAYOUT_ADDED);
                    }

                    @Override
                    public void onChildLayoutRemoved() {
                        queue.add(CHILD_LAYOUT_REMOVED);
                    }
                };

        // Open 'depthToTest' number of windows.
        InstrumentationRegistry.getInstrumentation()
                .runOnMainSync(
                        () -> {
                            mTestActivity.setTestCallback(testCallback);
                            WebView webView = mTestActivity.getTestRunnerWebView();
                            webView.loadDataWithBaseURL(
                                    "https://some.domain.test/",
                                    MULTIPLE_OPEN_CLOSE_TEST_WINDOW_SCRIPT,
                                    "text/html",
                                    null,
                                    null);
                        });
        // Wait until the last creation has been finished.
        for (int i = 0; i < depthToTest; ++i) {
            assertNextElementFromQueue(
                    i + "-th child window should be added.", CHILD_LAYOUT_ADDED, queue);
        }
        // Close the windows in reverse order.
        for (int i = depthToTest - 1; i >= 0; --i) {
            WebView webView = webViewList.get(i);
            // Add a delay here due to https://crbug.com/1002727.
            InstrumentationRegistry.getInstrumentation()
                    .runOnMainSync(
                            () -> {
                                String js = "window.close();";
                                new Handler()
                                        .postDelayed(
                                                () -> webView.evaluateJavascript(js, null),
                                                ON_CREATE_WINDOW_DELAY_MS);
                            });
            assertNextElementFromQueue(
                    i + "-th child window should be removed.", CHILD_LAYOUT_REMOVED, queue);
        }
    }

    private void assertNextElementFromQueue(String msg, int expected, BlockingQueue<Integer> queue)
            throws Exception {
        Integer element = queue.poll(TEST_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
        if (element == null) throw new TimeoutException("Timeout while asserting: " + msg);
        Assert.assertEquals(msg, Integer.valueOf(expected), element);
    }
}