Commit 0a566613 authored by Andrew Luo's avatar Andrew Luo Committed by Commit Bot

Code Lab to help developers ramp up on using the Page Controller test framework.

See https://chromium.googlesource.com/chromium/src/+/HEAD/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/README.md.

Bug: 924194
Change-Id: I4ac12207fcd54125f5741021a9450c6c061bfbf3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1838306
Commit-Queue: Andrew Luo <aluo@chromium.org>
Reviewed-by: default avatarSky Malice <skym@chromium.org>
Reviewed-by: default avatarJohn Budorick <jbudorick@chromium.org>
Cr-Commit-Position: refs/heads/master@{#706533}
parent a077e0de
...@@ -7,8 +7,9 @@ import("//build/config/android/rules.gni") ...@@ -7,8 +7,9 @@ import("//build/config/android/rules.gni")
android_library("chrome_java_test_pagecontroller") { android_library("chrome_java_test_pagecontroller") {
testonly = true testonly = true
java_files = [ java_files = [
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/android/PermissionDialog.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ElementController.java", "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ElementController.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/PageController.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/android/PermissionDialog.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/first_run/DataSaverController.java", "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/first_run/DataSaverController.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/first_run/SyncController.java", "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/first_run/SyncController.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/first_run/TOSController.java", "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/first_run/TOSController.java",
...@@ -19,7 +20,6 @@ android_library("chrome_java_test_pagecontroller") { ...@@ -19,7 +20,6 @@ android_library("chrome_java_test_pagecontroller") {
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/IncognitoNewTabPageController.java", "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/IncognitoNewTabPageController.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/NewTabPageController.java", "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/NewTabPageController.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/SuggestionTileController.java", "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/SuggestionTileController.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/PageController.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/tabswitcher/TabSwitcherController.java", "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/tabswitcher/TabSwitcherController.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/tabswitcher/TabSwitcherMenuController.java", "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/tabswitcher/TabSwitcherMenuController.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/urlpage/UrlPage.java", "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/urlpage/UrlPage.java",
...@@ -70,6 +70,22 @@ instrumentation_test_apk("chrome_java_test_pagecontroller_tests") { ...@@ -70,6 +70,22 @@ instrumentation_test_apk("chrome_java_test_pagecontroller_tests") {
} }
} }
instrumentation_test_apk("chrome_java_test_pagecontroller_codelab") {
apk_name = "ChromePageControllerCodelab"
apk_under_test = "//chrome/android:chrome_public_apk"
android_manifest = "javatests/src/org/chromium/chrome/test/pagecontroller/tests/AndroidManifest.xml"
java_files = [ "javatests/src/org/chromium/chrome/test/pagecontroller/tests/codelab/SettingsForCodelabTest.java" ]
deps = [
":chrome_java_test_pagecontroller",
"//third_party/junit",
]
if (!is_java_debug) {
proguard_enabled = true
proguard_configs = [ "//chrome/android/java/apk_for_test.flags" ]
}
}
junit_binary("chrome_java_test_pagecontroller_junit_tests") { junit_binary("chrome_java_test_pagecontroller_junit_tests") {
testonly = true testonly = true
java_files = [ java_files = [
......
...@@ -7,17 +7,79 @@ by organizing the logic to interact with the various application UI components ...@@ -7,17 +7,79 @@ by organizing the logic to interact with the various application UI components
into re-usable classes. Learn how to write and use Page Controllers to solve into re-usable classes. Learn how to write and use Page Controllers to solve
your testing needs. your testing needs.
## File Organization ## File Organization
[codelabsolution](codelabsolution]: Solutions for the [code lab](#code-lab).
[controllers](https://cs.chromium.org/chromium/src/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/controllers/): Contains all the Page Controllers.<br/> [controllers](https://cs.chromium.org/chromium/src/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/controllers/): Contains all the Page Controllers.<br/>
[rules](https://cs.chromium.org/chromium/src/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/rules/): Junit Test Rules that provide access to the Page Controllers in a [rules](https://cs.chromium.org/chromium/src/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/rules/): Junit Test Rules that provide access to the Page Controllers in a
test case.<br/> test case.<br/>
[tests](https://cs.chromium.org/chromium/src/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/tests/): Tests for the Page Controllers themselves.<br/> [tests](https://cs.chromium.org/chromium/src/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/tests/): Tests for the Page Controllers themselves.<br/>
[utils](https://cs.chromium.org/chromium/src/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/utils/): Utility classes that are useful for writing Page Controllers.<br/> [utils](https://cs.chromium.org/chromium/src/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/utils/): Utility classes that are useful for writing Page Controllers.<br/>
## Using IDEs
IDEs will make writing tests and maintaining Page Controllers much easier with online documentation and code completion. See the [setup
guide](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/README.md#integrated-development-environment-ide_set-up-guides).
## Code Lab
The code lab is an introduction to writing new Page Controllers and using them in tests.
1) Create a new branch to go through the exercise, ex:
```
git checkout -b pagecontroller_codelab
```
2) Add code lab files to the build by adding the following lines to chrome/test/android/BUILD.gn.
```
...
android_library("chrome_java_test_pagecontroller") {
testonly = true
java_files = [
...
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/android/PermissionDialog.java",
+ "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/codelab/SearchEngineSelectionControllerForCodelab.java",
+ "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/codelab/SettingsControllerForCodelab.java",
"javatests/src/org/chromium/chrome/test/pagecontroller/controllers/first_run/DataSaverController.java",
...
]
```
3) Modify the following files according to TODO hints contained therein. It's
best to build and test frequently iteratively as you complete the TODO items.
- Start here: [SettingsForCodelabTest.java](tests/codelab/SettingsForCodelabTest.java)
- Complete TODOs in: [controllers/ntp/ChromeMenu.java](controllers/ntp/ChromeMenu.java)
(Hint, Read the ["Where to find the resource and string
ids"](#where-to-find-the-resource-and-string-ids) section to find resource and
string ids to use in locators.)
- Complete TODOs to pass SettingsForCodelabTest.testOpenCodelabSettings: [controllers/codelab/SettingsControllerForCodelab.java](controllers/codelab/SettingsControllerForCodelab.java)
- Complete TODOs to pass SettingsForCodelabTest.testSwitchSearchEngine:
[controllers/codelab/SearchEngineSelectionControllerForCodelab.java](controllers/codelab/SearchEngineSelectionControllerForCodelab.java)
4) Build the codelab.
```
autoninja -C out/Debug chrome_java_test_pagecontroller_codelab
```
5) Run codelab tests (repeat from step 3 until all the tests pass).
```
# Open the html results link to view logcat to debug errors. There will be an
# xml hierarchy dump of the UI in case of test failures in the logcat.
# Screenshot is also available in the test results page.
# Run the tests one at a time (to aid in troubleshooting) by using the -f option:
out/Debug/bin/run_chrome_java_test_pagecontroller_codelab --num-retries 0 --local-output -f "*SettingsForCodelabTest.testOpenSettings"
out/Debug/bin/run_chrome_java_test_pagecontroller_codelab --num-retries 0 --local-output -f "*SettingsForCodelabTest.testSwitchSearchEngine"
```
6) Sample answers are [here](codelabsolution/README.md) in case you get stuck.
## Writing Testcases ## Writing Testcases
See the [ExampleTest](https://cs.chromium.org/chromium/src/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/tests/ExampleTest.java) See the [ExampleTest](https://cs.chromium.org/chromium/src/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/tests/ExampleTest.java).
## Creating + Updating Page Controllers ## Creating + Updating Page Controllers
...@@ -60,3 +122,40 @@ class CoolNewPageController extends PageController { ...@@ -60,3 +122,40 @@ class CoolNewPageController extends PageController {
} }
} }
``` ```
## Where to find the resource and string ids.
If you're working on a new UI change or are familiar with the UI that you want
to implement a Page Controller for, then you already should know what R.id.* and
R.string.* entries to use in the Page Controllers. Prefer to construct locators
with these resources, for example:
```
import org.chromium.chrome.R;
...
IUi2Locator LOCATOR_TAB_SWITCHER_BUTTON = Ui2Locators.withResEntries(R.id.tab_switcher_button);
IUi2Locator LOCATOR_NEW_TAB_MENU_ITEM =
Ui2Locators.withPath(Ui2Locators.withResEntries(R.id.menu_item_text),
Ui2Locators.withText(R.string.menu_new_tab));
```
**It is highly recommended that unique R.id entries are present for important UI elements in an activity, add if they're missing.**
**It is also highly recommended that R.string.\* entries be used in Page Controllers and tests instead of hard-coding them. This will keep tests in sync with the code-base and allow them to work under different language settings.**
If you are not too sure about the correct resource / string ids to use, see if you can find the developer or maintainer for the UI. If they're not available, here are some tips:
- [Android Layout inspector](https://developer.android.com/studio/debug/layout-inspector): This is useful to find out the resource ids (click an element, then expand the properties list, under mID). To see strings, expand the text folder, see the mText field. Note that this is the literal string, not the string resource id (see strings grd file below).
- The res directory: Search in [chrome/android/java/res/](https://cs.chromium.org/chromium/src/chrome/android/java/res/) directory for resource ids and string ids. They are defined there and then auto generated (@+id/xyz -> R.id.xyz)
- Search the strings grd file with
[search_strings.py](https://cs.chromium.org/chromium/src/tools/android/pagecontroller/search_strings.py).
[android_chrome_strings.grd](https://cs.chromium.org/chromium/src/chrome/android/java/strings/android_chrome_strings.grd) contains the list of strings used in Clank.
The entries are transformed into android strings using the name attribute by dropping the IDS_ prefix and converting the rest into lower case. For example: IDS_MENU_BOOKMARKS -> R.string.menu_bookmarks. There may be several string matching what's displayed but with different ids, be sure to read the "desc" field in the grd file and also the java source code for the UI to pick the right one.
- The Clank java source code: [src/chrome/android/java/src/org/chromium/chrome/browser/](https://cs.chromium.org/chromium/src/chrome/android/java/src/org/chromium/chrome/browser/)
Search for ids and strings discovered in the previous steps to see how they are used.
// Copyright 2019 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.test.pagecontroller.controllers.codelab;
import org.chromium.chrome.test.pagecontroller.controllers.PageController;
// TODO: Implement page controller for SearchEnginePreferences.java.
/**
* Search Engine Selection Page Controller for the Code Lab, representing
* SearchEnginePreferences.java.
*
* @see <a
* href="https://chromium.googlesource.com/chromium/src/+/HEAD/chrome/android/java/src/org/chromium/chrome/browser/preferences/SearchEnginePreference.java">SearchEnginePreference.java</a>
*/
public class SearchEngineSelectionControllerForCodelab extends PageController {
// The next 5 lines are boilerplate, no need to modify.
private static final SearchEngineSelectionControllerForCodelab sInstance =
new SearchEngineSelectionControllerForCodelab();
private SearchEngineSelectionControllerForCodelab() {}
public static SearchEngineSelectionControllerForCodelab getInstance() {
return sInstance;
}
@Override
public SearchEngineSelectionControllerForCodelab verifyActive() {
// TODO: Implement this method to verify that the UI is displaying the
// search engine selection activity then return this (otherwise throw).
return this;
}
/**
* Choose the Omnibox default search engine.
* @param engine The engine to choose.
* @return SearchEngineSelectionControllerForCodelab, after verification that
* the page has transitioned to it.
*/
public SearchEngineSelectionControllerForCodelab chooseSearchEngine(String engineName) {
// TODO: Construct a IUi2Locator for the element corresponding to the
// given engineName and perform a click on it.
// (Hint, the resource id entry for search engine choices along with the
// engineName can be used by Ui2Locators.withPath(...).)
return this;
}
/**
* @return The current engine choice.
*/
public String getEngineChoice() {
// TODO: Determine which engine option is selected and return it.
// (Hint, there are various get*Checked methods in UILocatorHelper
// to find out if something is checked.)
return null;
}
}
// Copyright 2019 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.test.pagecontroller.controllers.codelab;
import org.chromium.chrome.test.pagecontroller.controllers.PageController;
import org.chromium.chrome.test.pagecontroller.utils.IUi2Locator;
// TODO: Implement page controller for MainPreferences.java. Read
// documentation in the PageController class and refer to implemented Page
// Controllers in the pagecontroller directory for examples.
/**
* Settings Menu Page Controller for the Code Lab, representing
* MainPreferences.java.
*
* @see <a
* href="https://chromium.googlesource.com/chromium/src/+/HEAD/chrome/android/java/src/org/chromium/chrome/browser/preferences/MainPreferences.java">MainPreferences.java</a>
*/
public class SettingsControllerForCodelab extends PageController {
// TODO: Replace null with a an actual locator. Hint, see
// {@link ../../README.md#where-to-find-the-resource-and-string-ids}, and
// {@link ../../utils/Ui2Locators#withTextString}.
static final IUi2Locator LOCATOR_SETTINGS = null;
// TODO: (Hint, you may need more IUi2Locators, add them here.
// The next 5 lines are boilerplate, no need to modify.
private static final SettingsControllerForCodelab sInstance =
new SettingsControllerForCodelab();
private SettingsControllerForCodelab() {}
public static SettingsControllerForCodelab getInstance() {
return sInstance;
}
@Override
public SettingsControllerForCodelab verifyActive() {
// TODO: See PageController.verifyActive documentation on what this
// method should do. (Hint, PageController has a mLocatorHelper field.)
return this;
}
/**
* Click on the Search Engine option.
* @returns SearchEngineSelectionControllerForCodelab PageController.
*/
public SearchEngineSelectionControllerForCodelab clickSearchEngine() {
// TODO: Perform a click on the Search engine option
// in the Settings menu. (Hint, PageController has a mUtils field.)
// TODO: Replace null with an instance of CodelabSearchEngine. (Hint,
// all PageController subclasses have a verifyActive method.)
return null;
}
}
// Copyright 2019 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.test.pagecontroller.tests.codelab;
import android.support.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.chrome.test.pagecontroller.controllers.ntp.ChromeMenu;
import org.chromium.chrome.test.pagecontroller.rules.ChromeUiApplicationTestRule;
import org.chromium.chrome.test.pagecontroller.rules.ChromeUiAutomatorTestRule;
/**
* Test for Page Controller Code Lab.
*/
@SmallTest
@RunWith(BaseJUnit4ClassRunner.class)
public class SettingsForCodelabTest {
// ChromeUiAutomatorTestRule will capture a screen shot and UI Hierarchy info in the event
// of a test failure to aid test debugging.
public ChromeUiAutomatorTestRule mUiAutomatorRule = new ChromeUiAutomatorTestRule();
// ChromeUiApplicationTestRule provides a way to launch the Chrome Application under test
// and access to the Page Controllers.
public ChromeUiApplicationTestRule mChromeUiRule = new ChromeUiApplicationTestRule();
// The rule chain allows deterministic ordering of test rules so that the
// UiAutomatorRule will print debugging information in case of errors
// before the application is shut-down.
@Rule
public final TestRule mChain = RuleChain.outerRule(mChromeUiRule).around(mUiAutomatorRule);
private ChromeMenu mChromeMenu;
@Before
public void setUp() {
// TODO: Obtain a ChromeMenu instance. Hint, start with
// mChromeUiRule.launchIntoNewTabPageOnFirstRun().
mChromeMenu = null;
}
@Test
public void testOpenCodelabSettings() {
// TODO: Uncomment and add a method to ChromeMenu that returns an
// instance of SettingsControllerForCodelab to pass this test.
// mChromeMenu.openSettingsForCodelab();
}
@Test
public void testSwitchSearchEngine() {
// TODO: Uncomment and implement clickSearchEngine that verifies the
// page has changed to the default search engine selection page.
// SearchEngineSelectionControllerForCodelab engineSelection =
// mChromeMenu.openSettingsForCodelab().clickSearchEngine();
// Assert.assertEquals(engineSelection.getEngineChoice(), "Google");
// TODO: Change the search engine to something else, then verify that
// the change is reflected in the UI.
}
}
file://chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/OWNERS
#!/usr/bin/env python
# Copyright 2019 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.
"""Finds R.string.* ids in android_chrome_sstrings.grd file."""
import argparse
import os
import re
import sys
import xml.etree.cElementTree as ElementTree
_SRC_DIR = os.path.abspath(os.path.join(
os.path.dirname(__file__), '..', '..', '..'))
_DEFAULT_GRD = os.path.join(_SRC_DIR,
'chrome/android/java/strings/android_chrome_strings.grd')
_MATCH_MSG = u' R.string.{}:\n Text = "{}"\n Desc = "{}"'
class GrdSearch(object):
""" Search strings.grd file. """
def __init__(self, grd_path):
grd_dom = ElementTree.parse(grd_path)
messages = grd_dom.findall('.//message')
self.message_lookup = {}
for message in messages:
self.message_lookup[message.get('name')] = {
'text': message.itertext().next().strip(),
'desc': message.get('desc')}
def find_term_in_grd(self, term, is_regex):
""" Returns matches for term in the form (string id, text, desc) """
results = []
for name,value in self.message_lookup.iteritems():
if ((not is_regex and value['text'] == term) or
(is_regex and re.match(term, value['text']))):
results.append((name[4:].lower(), value['text'], value['desc']))
return results
def main():
parser = argparse.ArgumentParser(description='Find clank string resource ids'
' based on string content.')
parser.add_argument('-r', '--regex', action='store_true',
help='perform regex search instead of literal')
parser.add_argument('-g', '--grd-file',
default=_DEFAULT_GRD,
help='strings.grd file, default: {}'.format(_DEFAULT_GRD))
parser.add_argument('terms', nargs='+',
help='Search terms.')
args = parser.parse_args(sys.argv[1:])
searcher = GrdSearch(args.grd_file)
for t in args.terms:
print('{} search term: "{}", R.string.* matches:'.format(
('Regex' if args.regex else 'Literal'), t))
for name, text, desc, in searcher.find_term_in_grd(t.decode('utf-8'),
args.regex):
print(_MATCH_MSG.format(name, text, desc))
print('')
if __name__ == '__main__':
sys.exit(main())
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment