{"id":15098084,"url":"https://github.com/ionic-team/ionic-e2e-example","last_synced_at":"2025-10-19T17:30:34.484Z","repository":{"id":44550401,"uuid":"428843013","full_name":"ionic-team/ionic-e2e-example","owner":"ionic-team","description":"Example app for Ionic E2E","archived":false,"fork":false,"pushed_at":"2024-05-23T15:17:55.000Z","size":64824,"stargazers_count":46,"open_issues_count":6,"forks_count":20,"subscribers_count":7,"default_branch":"main","last_synced_at":"2024-10-02T06:21:30.158Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ionic-team.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-11-16T23:14:50.000Z","updated_at":"2024-08-10T06:58:09.000Z","dependencies_parsed_at":"2024-09-15T16:18:05.210Z","dependency_job_id":null,"html_url":"https://github.com/ionic-team/ionic-e2e-example","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionic-team%2Fionic-e2e-example","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionic-team%2Fionic-e2e-example/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionic-team%2Fionic-e2e-example/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ionic-team%2Fionic-e2e-example/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ionic-team","download_url":"https://codeload.github.com/ionic-team/ionic-e2e-example/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":219869264,"owners_count":16555575,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-09-25T16:43:13.599Z","updated_at":"2025-10-19T17:30:32.902Z","avatar_url":"https://github.com/ionic-team.png","language":"TypeScript","readme":"# Ionic E2E Example\n\nThis example app demonstrates how to build web and native end-to-end (E2E) tests with Ionic and Cordova or Capacitor. This example uses popular tools like [WebdriverIO](https://webdriver.io) and [Appium](https://appium.io) to enable cross-platform tests for iOS, Android, Web, and more.\n\nAdditionally, this example comes with some helpers that make it easier to write tests against Ionic or hybrid apps in general.\n\nNote: this example app uses Cordova as it's based on the older Ionic Conference App, but we strongly recommend teams use [Capacitor](https://capacitorjs.com/) and the same information below will apply.\n\n## About the Testing Stack\n\nWe've chosen [WebdriverIO](https://webdriver.io) as the primary test runner and API for test authoring. WebdriverIO is the leading Node.js-based test automation framework and supports a wide variety of tools that support the WebDriver protocol.\n\nWebdriverIO orchestrates tools like [Appium](https://appium.io) and Chromedriver to actually run tests on target devices or a web browser.\n\nOne of the benefits of this stack compared to popular tools like [Cypress.io](https://cypress.io) is that it can test your actual native app, the same app that you'll ship to end users, but with a similar test authoring API.\n\n## Developing Tests\n\nOne of the benefits to Web Native development is the ability to build most of your app in a browser. End-to-end testing should be no different. To facilitate this, we've provided a develop mode that connects to a running `ionic serve` or other dev server:\n\n`SERVE_PORT=8101 npm run ionic-e2e:develop`\n\nWhere `SERVE_PORT` is the port your local dev server is running.\n\nThis will start a web-based test development server in Chrome and watch for changes to your tests so you can rapidly develop them.\n\n\u003e **NOTE:**\n\u003e The default port is `8100`, but when running Appium tests the default port will conflict with the WDA-agent.\n\n## Building App Binaries\n\nBefore running tests, native binaries for iOS (if on Mac) and Android need to be built.\n\nWhen building for Android, the following environment variables must be set:\n\n`ANDROID_SDK_ROOT`, `JAVA_HOME`, and Gradle must be on your path for this sample (i.e. `export PATH=$PATH:/path/to/gradle-x.x.x/bin`)\n\nTo build for iOS:\n\n`npm run ionic-e2e:build:ios`\n\n## Running Tests\n\nTo run your tests on actual native devices and emulators/simulators, use the following commands depending on the platform you'd like to run on:\n\n```typescript\nnpm run ionic-e2e:run:ios\nnpm run ionic-e2e:run:android\nnpm run ionic-e2e:run:web\n```\n\n\u003e **NOTE:** Because this conference apps starts with a Webview the [Android](tests/config/wdio.android.config.ts) and [iOS](tests/config/wdio.ios.config.ts) \nconfigs will automatically set the webview for you with this capability `appium:autoWebview`. This means you don't need to switch to the correct Webview yourself.\n\n## Configuring WebdriverIO and Appium\n\nEdit `config/wdio.[platform].config.ts` based on the target platform to configure the settings and capabilities for the test.\n\n## Exploring the Tests\n\nExplore the [tests](https://github.com/ionic-team/ionic-e2e-example/tree/main/tests) folder to find a typical [Page Object Pattern](https://webdriver.io/docs/pageobjects/) test layout. In the `pageobjects` folder we find a typescript file corresponding to every logical page in our app. A Page Object is a testing abstraction over a page that defines properties and methods that we will interact with in our test specs.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/tutorial-page.png\" width=\"320\" /\u003e\n\u003c/p\u003e\n\nFor example, see the [Tutorial](https://github.com/ionic-team/ionic-e2e-example/blob/main/tests/pageobjects/tutorial.page.ts) Page Object, which defines the `slides`, `skipButton`, and `continueButton` properties corresponding to elements on the page that we will interact with in our test specs. Additionally, we define the `swipeLeft()`, `swipeRight()`, `skip()`, and `continue()` methods which are actions we will take against the page in our test specs.\n\n```typescript\nimport { IonicButton, IonicSlides } from \"../helpers\";\nimport Page from \"./page\";\n\nclass Tutorial extends Page {\n  get slides() {\n    return new IonicSlides(\"swiper\");\n  }\n  get skipButton() {\n    return IonicButton.withTitle(\"Skip\");\n  }\n  get continueButton() {\n    return IonicButton.withTitle(\"Continue\");\n  }\n\n  async swipeLeft() {\n    return this.slides.swipeLeft();\n  }\n\n  async swipeRight() {\n    return this.slides.swipeRight();\n  }\n\n  async skip() {\n    return this.skipButton.tap();\n  }\n\n  async continue() {\n    await this.continueButton.tap();\n  }\n}\n\nexport default new Tutorial();\n```\n\nWe see that this is a simple representation of our page, exposing components and methods that our tests will need to interact with, and nothing more.\n\nIn the `specs` folder we find test specs corresponding to each page, and this is where our actual test assertions live. Let's explore the [Tutorial Test Spec](https://github.com/ionic-team/ionic-e2e-example/blob/main/tests/specs/app.tutorial.spec.ts) to see the actual tests performed against the Tutorial Page Object.\n\n```typescript\nimport {\n  clearIndexedDB,\n  pause,\n  getUrl,\n  url,\n  setDevice,\n  switchToWeb,\n  Device,\n  waitForLoad,\n} from \"../helpers\";\n\ndescribe(\"tutorial\", () =\u003e {\n  before(async () =\u003e {\n    await waitForLoad();\n  });\n\n  beforeEach(async () =\u003e {\n    await switchToWeb();\n    await url(\"/tutorial\");\n    await setDevice(Device.Mobile);\n    await clearIndexedDB(\"_ionicstorage\");\n  });\n\n  it(\"Should get to schedule\", async () =\u003e {\n    await Tutorial.swiper.swipeLeft();\n    await Tutorial.swiper.swipeLeft();\n    await Tutorial.swiper.swipeLeft();\n\n    await Tutorial.continue();\n\n    await pause(1000);\n\n    await expect((await getUrl()).pathname).toBe(\"/app/tabs/schedule\");\n  });\n\n  /* ... more tests ... */\n});\n```\n\nIn this test spec we first wait for the webview to load before we run any tests (that's our `waitForLoad` helper), next before each test we do four things: Switch to the Web View context, set the current url to `/tutorial`, set the device to mobile, and then clear IndexedDB since this page uses it to store a flag indicating whether the tutorial was finished.\n\nFor the test shown above, note that we merely interact with the methods available on the Page Object for the Tutorial page. We `swipeLeft()` three times which progresses the slides, and then we `continue()`. We wait one second for the client-side navigation to occur, then verify we navigated to the `/app/tabs/schedule` page.\n\nUsing this strategy we can build tests for any of our pages while keeping our test specs short and not reliant on DOM structure or any details that could break with small design or layout changes in the app.\n\n## Test Helpers\n\nThis example comes with a number of test helpers specifically meant for testing hybrid apps and those using Ionic's UI components. Some of these helpers merely wrap [WebdriverIO API calls](https://webdriver.io/docs/api) to make them simpler, but some specifically operate on the unique structure of Ionic apps and are important to use.\n\n### Querying Elements\n\nFor developers using Ionic's UI components and Ionic Angular/React/Vue, the most important helper is `Ionic$` and the related Ionic components in [helpers/ionic/components](https://github.com/ionic-team/ionic-e2e-example/tree/main/tests/helpers/ionic/components).\n\nIonic is highly optimized for performance, and because of that it does a few things that can trick the built-in `$` and `$$` queries, since elements might be in the DOM but are not visible, and querying for elements without considering this can cause tests to fail.\n\nInstead, when using Ionic, always use the `Ionic$` helper to query elements, or one of the special Ionic components as listed below.\n\n```typescript\nimport { Ionic$ } from \"../helpers\";\n\nawait Ionic$.$(\"#one-element\");\nawait Ionic$.$$(\".multiple-elements\");\n```\n\n## Everything is Async\n\nOne thing to note about the test API: everything is async. Because WebdriverIO is sending commands to a server that is controlling your app, everything is done asynchronously. Thus, always `await` any time you query for an element or perform any action. Forgetting to wait for a promise to resolve is a common mistake to make when writing tests!\n\n### Ionic Component Helpers\n\nIn [helpers/ionic/components](https://github.com/ionic-team/ionic-e2e-example/tree/main/tests/helpers/ionic/components) there are a number of classes that make interacting with Ionic components easier. Here are some particularly useful ones:\n\n- IonicAlert\n- IonicButton\n- IonicInput\n- IonicTextarea\n- IonicMenu\n- IonicPage\n- IonicSegment\n- IonicSelect\n- IonicSlides\n- IonicToast\n\nHere are some examples of using these helpers:\n\n```typescript\nimport { IonicAlert, IonicButton, IonicInput, IonicMenu, IonicPage, IonicSegment, IonicSelect, IonicSlides, IonicTextarea } from '../helpers';\n\n// Alert\nconst alert = new IonicAlert();\nconst input = await alert.input;\ninput.setValue('My Response');\nconst okButton = alert.button('Ok');\nawait okButton.click();\n\n// Button\nconst button = IonicButton.withTitle(\"Sign up\");\nawait button.tap();\n\n// Input and Textarea\nconst username = new IonicInput(\"#username\");\nawait username.setValue(\"ionitron\");\nconst value = await username.getValue();\nconst message = new IonicTextarea(\"#message\");\nawait message.setValue(\"This is a long message\");\n\n// Menu\nconst menu = new IonicMenu(); // Will find the first menu if no selector provided\nawait menu.open();\n\n// Page\nconst activePage = await IonicPage.active();\n\n// Segment\nconst segment = new IonicSegment('#my-segment');\nconst favoritesButton = await segment.button('Favorites');\nawait favoritesButton.tap();\n\n// Select\nconst select = new IonicSelect('#my-select');\nawait select.open();\nawait select.select(0);\nawait select.ok();\n// await select.cancel()\n\n// Slides\nawait slides = new IonicSlides('#my-slides');\nawait slides.swipeLeft();\nawait slides.swipeRight();\n\n// Toast\nconst toast = new IonicToast();\nconst currentToastText = await toast.getText();\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fionic-team%2Fionic-e2e-example","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fionic-team%2Fionic-e2e-example","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fionic-team%2Fionic-e2e-example/lists"}