Mike Baxley | 47db7d8 | 2017-11-16 15:57:17 | [diff] [blame] | 1 | # Automated testing for Chrome for iOS |
| 2 | |
| 3 | See the [instructions] for how to check out and build Chromium for iOS. |
| 4 | |
| 5 | Automated testing is a crucial part of ensuring the quality of Chromium. |
| 6 | |
| 7 | ## Unit testing |
| 8 | |
Raphael Kubo da Costa | 310c40c | 2025-01-21 15:25:56 | [diff] [blame] | 9 | Unit testing is done via gtests. The easiest way to run a unit test is to use |
| 10 | the wrapper scripts generated at build time: |
| 11 | |
| 12 | ```sh |
| 13 | |
| 14 | ``` |
| 15 | |
| 16 | The wrapper scripts take care of invoking `//ios/build/bots/scripts/run.py` |
| 17 | with options like `--iossim` set appropriately. In general, you need to pass |
| 18 | at least `--out-dir` (a directory where test results will be stored), |
| 19 | `--platform` (a device available to the simulator), `--xcode-build` (obtained |
| 20 | via e.g. About XCode) and `--version` (iOS version to run). The |
| 21 | `--gtest_filter` option is also supported. |
| 22 | |
| 23 | A more complete example looks like this: |
| 24 | |
| 25 | ```sh |
| 26 | out/Debug-iphonesimulator/run_base_unittests \ |
| 27 | --gtest_filter=Base64Test.Basic \ |
| 28 | --platform "iPhone 16" \ |
| 29 | --version 18.2 \ |
| 30 | --xcode-build 16c5032a |
| 31 | ``` |
Mike Baxley | 47db7d8 | 2017-11-16 15:57:17 | [diff] [blame] | 32 | |
| 33 | ## Integration testing |
| 34 | |
Zhaoyang Li | 27002046 | 2020-10-27 23:07:29 | [diff] [blame] | 35 | [EarlGrey] (EG2) is the integration testing framework used by Chromium for iOS. |
| 36 | |
| 37 | ### Writing EarlGrey tests |
| 38 | |
| 39 | #### Before you start |
| 40 | |
| 41 | * Just write a unit test if the purpose of your test does not involve UI. |
| 42 | * Learn about EarlGrey test framework principles and APIs in [EarlGrey]. |
| 43 | * Learn about [Defining Test Cases and Test Methods] from Apple. |
| 44 | |
| 45 | #### Creating test files and writing EG2 tests |
| 46 | |
| 47 | 1. EG2 test files are ended with _egtest.mm, and usually located within the same |
| 48 | directory of the UI code you wish to test. |
| 49 | 2. Basic imports of a EG2 test file: |
| 50 | |
| 51 | * You’ll have to include: |
| 52 | ``` |
Ernesto Izquierdo Clua | ab3923fd | 2022-03-01 20:42:15 | [diff] [blame] | 53 | #import "ios/chrome/test/earl_grey/chrome_test_case.h" |
Zhaoyang Li | 27002046 | 2020-10-27 23:07:29 | [diff] [blame] | 54 | ``` |
| 55 | * You’ll most likely find util functions in these files helpful. |
| 56 | ``` |
| 57 | #import "ios/chrome/test/earl_grey/chrome_earl_grey.h" |
| 58 | #import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h" |
| 59 | #import "ios/chrome/test/earl_grey/chrome_matchers.h" |
| 60 | ``` |
| 61 | * Beside these, directly import an EG2 header for an EG2 API you are using. |
| 62 | |
| 63 | 3. TestCase/testMethods definitions. Create `SomeGreatTestCase` as a subclass of |
| 64 | `ChromeTestCase`. Create test methods, eg `-(void)testMyGreatUIFeature {...}`, |
| 65 | and put UI actions within the test methods. |
| 66 | * Put your setup and tear down code for the *TestCase* in |
| 67 | `+(void)setUpForTestCase` and `+tearDown`. These will run once before and |
| 68 | after all tests for the test class. |
| 69 | * Put your setup and tear down code for each *test method* in `-(void)setUp` |
| 70 | and `-(void)tearDown`. These will run before and after every |
| 71 | `-(void)testMethod` in the file. |
| 72 | 4. Writing test contents. See the chrome helpers (imports in 2.) as well as |
| 73 | [EarlGrey APIs] to write a UI action/assertion in your testMethod. |
| 74 | |
| 75 | #### Interacting with the app in a test |
| 76 | |
| 77 | ##### Relaunch app with different flags |
| 78 | |
| 79 | In EG2 tests, the test process launches the host app process at the beginning, |
| 80 | then runs UI actions/assertions in the app. To pass args or feature flags to the |
| 81 | app at initial launching, or relaunch the app in the middle of your test, see |
| 82 | [this AppLaunchManager API]. |
| 83 | |
| 84 | ##### Accessing app internals |
| 85 | |
| 86 | EG2 test targets are built with test-related code but without app code. |
| 87 | |
| 88 | To access anything from the app side, use an "app interface". App interface is |
| 89 | implemented as a class that lives in the app process, but can be accessed in the |
| 90 | test process through [eDO]. You can include the header in your test side code |
| 91 | and call class methods of the interface class. The methods will execute code in |
| 92 | the app process and can return basic Objective-C types. See this [Example of App |
| 93 | Interface]. |
| 94 | |
| 95 | See `eg_test_support+eg2` (test side utilities) and `eg_app_support+eg2` (app |
| 96 | side utilities) targets in `BUILD.gn` files to learn how test utilities are |
| 97 | organized in targets. If you added an app side helper (app interface), you’ll |
| 98 | also need to include your new `eg_app_support+eg2` target in |
| 99 | `//ios/chrome/test/earl_grey/BUILD.gn`’s `eg_app_support+eg2` target. ([Example |
| 100 | CL adding App Interface]). |
| 101 | |
| 102 | Note that if you create an App interface, you can’t build the app interface |
Sylvain Defresne | a852434c | 2021-10-15 07:53:39 | [diff] [blame] | 103 | class in your eg2_tests target, but you need to include and refer to it. To |
| 104 | satisfy the linker, you'll need to create a `my_test_app_interface_stub.mm` |
| 105 | file with the following content in it and build it as a dependency of your |
| 106 | tests that use the app interface. |
| 107 | |
| 108 | ```objc |
| 109 | #import "ios_internal/chrome/test/earl_grey2/my_test_app_interface.h" |
| 110 | |
Cameron Higgins | 0e35019 | 2023-03-21 17:43:45 | [diff] [blame] | 111 | #import "ios/testing/earl_grey/earl_grey_test.h" |
Sylvain Defresne | a852434c | 2021-10-15 07:53:39 | [diff] [blame] | 112 | |
Sylvain Defresne | a852434c | 2021-10-15 07:53:39 | [diff] [blame] | 113 | GREY_STUB_CLASS_IN_APP_MAIN_QUEUE(MyTestAppInterface) |
| 114 | |
Zhaoyang Li | 27002046 | 2020-10-27 23:07:29 | [diff] [blame] | 115 | ``` |
Sylvain Defresne | a852434c | 2021-10-15 07:53:39 | [diff] [blame] | 116 | |
| 117 | If you don't you'll get linker errors that read like “Undefined symbols for |
| 118 | architecture… MyTestAppInterface” |
Zhaoyang Li | 27002046 | 2020-10-27 23:07:29 | [diff] [blame] | 119 | |
| 120 | #### Creating test targets and adding the target to test suites |
| 121 | |
| 122 | 1. Create a test target. Add a target(`source_set`) named "eg2_tests" into the |
| 123 | closest `BUILD.gn` file. Put the test file into the `sources` array and put the |
| 124 | targets containing headers used in your test file into `deps` array. This is to |
| 125 | organize test source files and dependencies so that the GN build system can |
| 126 | correctly build the test module. The skeleton of the target: |
| 127 | ``` |
| 128 | source_set("eg2_tests") { |
| 129 | configs += [ |
Zhaoyang Li | 27002046 | 2020-10-27 23:07:29 | [diff] [blame] | 130 | "//build/config/ios:xctest_config", |
| 131 | ] |
| 132 | testonly = true |
| 133 | sources = [ |
| 134 | "some_egtest.mm" |
| 135 | ] |
| 136 | deps = [ |
| 137 | "//ios/chrome/test/earl_grey:eg_test_support+eg2", |
| 138 | "//ios/testing/earl_grey:eg_test_support+eg2", |
Zhaoyang Li | 27002046 | 2020-10-27 23:07:29 | [diff] [blame] | 139 | ] |
Arthur Milchior | 54e9dc2 | 2022-09-21 13:41:52 | [diff] [blame] | 140 | frameworks = [ "UIKit.framework" ] |
Zhaoyang Li | 27002046 | 2020-10-27 23:07:29 | [diff] [blame] | 141 | } |
| 142 | ``` |
| 143 | 2. Include your test target in the `deps` array of a suitable suite in |
| 144 | `//src/ios/chrome/test/earl_grey2/BUILD.gn`. |
| 145 | 3. Optional: If you feel like your new test should be in a new suite, or you |
| 146 | want to delete an existing suite to make tests better organized, you’ll need to |
| 147 | change the suites in `//src/ios/chrome/test/earl_grey2/BUILD.gn` in the format |
| 148 | of existing ones. (Do not forget to [config the bots] so the new suite can run |
| 149 | in infra.) |
| 150 | 4. Ensure your dependencies are correct. |
| 151 | ``` |
| 152 | $ gn gen --check out/Debug-iphonesimulator |
| 153 | ``` |
Mike Baxley | 47db7d8 | 2017-11-16 15:57:17 | [diff] [blame] | 154 | |
| 155 | ### Running EarlGrey tests |
| 156 | |
| 157 | EarlGrey tests are based on Apple's [XCUITest]. |
| 158 | |
| 159 | #### Running tests from Xcode |
| 160 | |
Zhaoyang Li | 27002046 | 2020-10-27 23:07:29 | [diff] [blame] | 161 | 1. If you added a new test file / suite, run `gclient runhooks` to sync for the |
| 162 | list of tests in Xcode. |
Victor Hugo Vianna Silva | 703246b | 2023-07-05 14:06:46 | [diff] [blame] | 163 | 2. Run a test suite(module), TestCase or testMethod in test navigator. |
Zhaoyang Li | 27002046 | 2020-10-27 23:07:29 | [diff] [blame] | 164 | Xcode will build the targets and run the test(s) you choose. Alternatively, |
| 165 | use ⌘+U to run all the tests. See Apple's [Running Tests and Viewing Results]. |
Victor Hugo Vianna Silva | 703246b | 2023-07-05 14:06:46 | [diff] [blame] | 166 | 3. You can pass extra arguments to the app process with `--extra-app-args`, e.g. |
| 167 | `--extra-app-args='--enable-features=Foo'`. |
| 168 | * This might not work consistently as tests can re-launch the app with |
| 169 | arbitrary command-line arguments. |
| 170 | |
Mike Baxley | 47db7d8 | 2017-11-16 15:57:17 | [diff] [blame] | 171 | |
| 172 | #### Running from the command-line |
| 173 | |
Zhaoyang Li | 27002046 | 2020-10-27 23:07:29 | [diff] [blame] | 174 | EG2 tests can run in the command line with test runner scripts. You’ll need to |
| 175 | build the targets before running tests in cmd. This is used by continuous |
| 176 | integration infra and thus not user friendly. Running UI tests directly in Xcode |
| 177 | is recommended. |
| 178 | |
| 179 | Important notes: |
| 180 | * The test runner can invoke mac_toolchain to install a new Xcode of the version |
| 181 | specified to the path specified. You may want to choose a different path from |
| 182 | your daily use Xcode. |
| 183 | * If test_cases is empty in --args-json, all tests will run. Specifying a |
| 184 | testMethod to run is currently not supported in the test runner. |
| 185 | |
Mike Baxley | 47db7d8 | 2017-11-16 15:57:17 | [diff] [blame] | 186 | Example: |
| 187 | ``` |
Zhaoyang Li | 27002046 | 2020-10-27 23:07:29 | [diff] [blame] | 188 | src/ios/build/bots/scripts/run.py |
| 189 | --app |
| 190 | src/out/Debug-iphonesimulator/ios_chrome_ui_eg2tests_module-Runner.app |
| 191 | --host-app |
| 192 | src/out/Debug-iphonesimulator/ios_chrome_eg2tests.app |
| 193 | --args-json |
| 194 | {"test_args": [], "xctest": false, "test_cases": ["ReadingListTestCase"], |
| 195 | "restart": false, "xcode_parallelization": true, "xcodebuild_device_runner": |
| 196 | false} |
| 197 | --out-dir |
| 198 | path/to/output/dir |
| 199 | --retries |
| 200 | 3 |
| 201 | --shards |
| 202 | 1 |
| 203 | --xcode-build-version |
| 204 | 11c29 |
| 205 | --mac-toolchain-cmd |
| 206 | path/to/mac_toolchain |
| 207 | --xcode-path |
| 208 | path/to/Xcode.app |
| 209 | --wpr-tools-path |
| 210 | NO_PATH |
| 211 | --replay-path |
| 212 | NO_PATH |
| 213 | --iossim |
| 214 | src/out/Debug-iphonesimulator/iossim |
| 215 | --platform |
| 216 | iPad (6th generation) |
| 217 | --version |
| 218 | 13.3 |
Mike Baxley | 47db7d8 | 2017-11-16 15:57:17 | [diff] [blame] | 219 | ``` |
Zhaoyang Li | 27002046 | 2020-10-27 23:07:29 | [diff] [blame] | 220 | The invocation args are logged. You can find the latest arg format at the |
| 221 | beginning of stdout from an infra test shard if the above doesn't work. |
Mike Baxley | 47db7d8 | 2017-11-16 15:57:17 | [diff] [blame] | 222 | |
| 223 | |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 224 | [config the bots]: https://chromium.googlesource.com/chromium/src/testing/+/refs/heads/main/buildbot/README.md#buildbot-testing-configuration-files |
Zhaoyang Li | 27002046 | 2020-10-27 23:07:29 | [diff] [blame] | 225 | [Defining Test Cases and Test Methods]: https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods?language=objc |
| 226 | [EarlGrey]: https://github.com/google/EarlGrey/tree/earlgrey2 |
| 227 | [EarlGrey APIs]: https://github.com/google/EarlGrey/blob/master/docs/api.md |
| 228 | [eDO]: https://github.com/google/eDistantObject |
Stepan Khapugin | d8ca0ad | 2023-11-28 12:49:06 | [diff] [blame] | 229 | [Example of App Interface]: https://cs.chromium.org/chromium/src/ios/chrome/browser/metrics/model/metrics_app_interface.h |
Zhaoyang Li | 27002046 | 2020-10-27 23:07:29 | [diff] [blame] | 230 | [Example CL adding App Interface]: https://chromium-review.googlesource.com/c/chromium/src/+/1919147 |
Mike Baxley | 47db7d8 | 2017-11-16 15:57:17 | [diff] [blame] | 231 | [instructions]: ./build_instructions.md |
Zhaoyang Li | 27002046 | 2020-10-27 23:07:29 | [diff] [blame] | 232 | [Running Tests and Viewing Results]: https://developer.apple.com/library/archive/documentation/DeveloperTools/Conceptual/testing_with_xcode/chapters/05-running_tests.html |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 233 | [this AppLaunchManager API]: https://source.chromium.org/chromium/chromium/src/+/main:ios/testing/earl_grey/app_launch_manager.h;drc=d0889865de20c5b3bc59d58674eb2dcc02dd2269;l=47 |
Mike Baxley | 47db7d8 | 2017-11-16 15:57:17 | [diff] [blame] | 234 | [XCUITest]: https://developer.apple.com/documentation/xctest |