Giter Club home page Giter Club logo

ionic-e2e-example's Introduction

Ionic E2E Example

This 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 and Appium to enable cross-platform tests for iOS, Android, Web, and more.

Additionally, this example comes with some helpers that make it easier to write tests against Ionic or hybrid apps in general.

Note: this example app uses Cordova as it's based on the older Ionic Conference App, but we strongly recommend teams use Capacitor and the same information below will apply.

About the Testing Stack

We've chosen WebdriverIO 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.

WebdriverIO orchestrates tools like Appium and Chromedriver to actually run tests on target devices or a web browser.

One of the benefits of this stack compared to popular tools like 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.

Developing Tests

One 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:

SERVE_PORT=8101 npm run ionic-e2e:develop

Where SERVE_PORT is the port your local dev server is running.

This will start a web-based test development server in Chrome and watch for changes to your tests so you can rapidly develop them.

NOTE: The default port is 8100, but when running Appium tests the default port will conflict with the WDA-agent.

Building App Binaries

Before running tests, native binaries for iOS (if on Mac) and Android need to be built.

When building for Android, the following environment variables must be set:

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)

To build for iOS:

npm run ionic-e2e:build:ios

Running Tests

To run your tests on actual native devices and emulators/simulators, use the following commands depending on the platform you'd like to run on:

npm run ionic-e2e:run:ios
npm run ionic-e2e:run:android
npm run ionic-e2e:run:web

NOTE: Because this conference apps starts with a Webview the Android and iOS configs 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.

Configuring WebdriverIO and Appium

Edit config/wdio.[platform].config.ts based on the target platform to configure the settings and capabilities for the test.

Exploring the Tests

Explore the tests folder to find a typical Page Object Pattern 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.

For example, see the Tutorial 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.

import { IonicButton, IonicSlides } from "../helpers";
import Page from "./page";

class Tutorial extends Page {
  get slides() {
    return new IonicSlides("swiper");
  }
  get skipButton() {
    return IonicButton.withTitle("Skip");
  }
  get continueButton() {
    return IonicButton.withTitle("Continue");
  }

  async swipeLeft() {
    return this.slides.swipeLeft();
  }

  async swipeRight() {
    return this.slides.swipeRight();
  }

  async skip() {
    return this.skipButton.tap();
  }

  async continue() {
    await this.continueButton.tap();
  }
}

export default new Tutorial();

We 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.

In 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 to see the actual tests performed against the Tutorial Page Object.

import {
  clearIndexedDB,
  pause,
  getUrl,
  url,
  setDevice,
  switchToWeb,
  Device,
  waitForLoad,
} from "../helpers";

describe("tutorial", () => {
  before(async () => {
    await waitForLoad();
  });

  beforeEach(async () => {
    await switchToWeb();
    await url("/tutorial");
    await setDevice(Device.Mobile);
    await clearIndexedDB("_ionicstorage");
  });

  it("Should get to schedule", async () => {
    await Tutorial.swiper.swipeLeft();
    await Tutorial.swiper.swipeLeft();
    await Tutorial.swiper.swipeLeft();

    await Tutorial.continue();

    await pause(1000);

    await expect((await getUrl()).pathname).toBe("/app/tabs/schedule");
  });

  /* ... more tests ... */
});

In 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.

For 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.

Using 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.

Test Helpers

This 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 to make them simpler, but some specifically operate on the unique structure of Ionic apps and are important to use.

Querying Elements

For 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.

Ionic 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.

Instead, when using Ionic, always use the Ionic$ helper to query elements, or one of the special Ionic components as listed below.

import { Ionic$ } from "../helpers";

await Ionic$.$("#one-element");
await Ionic$.$$(".multiple-elements");

Everything is Async

One 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!

Ionic Component Helpers

In helpers/ionic/components there are a number of classes that make interacting with Ionic components easier. Here are some particularly useful ones:

  • IonicAlert
  • IonicButton
  • IonicInput
  • IonicTextarea
  • IonicMenu
  • IonicPage
  • IonicSegment
  • IonicSelect
  • IonicSlides
  • IonicToast

Here are some examples of using these helpers:

import { IonicAlert, IonicButton, IonicInput, IonicMenu, IonicPage, IonicSegment, IonicSelect, IonicSlides, IonicTextarea } from '../helpers';

// Alert
const alert = new IonicAlert();
const input = await alert.input;
input.setValue('My Response');
const okButton = alert.button('Ok');
await okButton.click();

// Button
const button = IonicButton.withTitle("Sign up");
await button.tap();

// Input and Textarea
const username = new IonicInput("#username");
await username.setValue("ionitron");
const value = await username.getValue();
const message = new IonicTextarea("#message");
await message.setValue("This is a long message");

// Menu
const menu = new IonicMenu(); // Will find the first menu if no selector provided
await menu.open();

// Page
const activePage = await IonicPage.active();

// Segment
const segment = new IonicSegment('#my-segment');
const favoritesButton = await segment.button('Favorites');
await favoritesButton.tap();

// Select
const select = new IonicSelect('#my-select');
await select.open();
await select.select(0);
await select.ok();
// await select.cancel()

// Slides
await slides = new IonicSlides('#my-slides');
await slides.swipeLeft();
await slides.swipeRight();

// Toast
const toast = new IonicToast();
const currentToastText = await toast.getText();

ionic-e2e-example's People

Contributors

3dd13 avatar adamdbradley avatar aristona avatar awebdeveloper avatar brandyscarney avatar danbucholtz avatar danielsogl avatar dependabot-preview[bot] avatar dependabot[bot] avatar dustinromey avatar frederickjansen avatar guillenotfound avatar imhoffd avatar jgw96 avatar kensodemann avatar liamdebeasi avatar manucorporat avatar matheo avatar matiastucci avatar mhartington avatar mlynch avatar nofrank avatar nphyatt avatar patrickjs avatar psamsotha avatar ramonornela avatar rdlabo avatar splaktar avatar tlancina avatar wswebcreation avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ionic-e2e-example's Issues

Broken build

I'm submitting a ... (check one with "x")
[x] bug report
[ ] feature request
[ ] support request => Please do not submit support requests here, use one of these channels: https://forum.ionicframework.com/ or http://ionicworldwide.herokuapp.com/

Current behavior:
The build is broken, see https://github.com/ionic-team/ionic-e2e-example/actions/runs/1740969551. This is because of the removal of Protractor and optimising the WDIO Test

Expected behavior:
A working build

See https://github.com/ionic-team/ionic-e2e-example/actions/runs/1740969551

Other information:
I will pick this up and create a new PR this week, can you please assign it to me?

Work with Chrome 100

I'm submitting a ... (check one with "x")
[X] bug report
[ ] feature request
[ ] support request => Please do not submit support requests here, use one of these channels: https://forum.ionicframework.com/ or http://ionicworldwide.herokuapp.com/

Current behavior:
Doesn't work with Chrome 100

Expected behavior:
Should work with Chrome 100

Steps to reproduce:

Related code:

insert any relevant code here

Other information:
To make work:

Edit package.json

"chromedriver": "100.0.0",

In terminal

npm install
npm run start

In another terminal

SERVE_PORT=4200 npm run ionic-e2e:run:web

Ionic info: (run ionic info from a terminal/cmd prompt and paste output below):

Ionic:

   Ionic CLI                     : 6.18.1 (/home/tim/.nvm/versions/node/v16.13.0/lib/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 6.0.0-rc.2
   @angular-devkit/build-angular : 13.0.1
   @angular-devkit/schematics    : 13.0.1
   @angular/cli                  : 13.0.1
   @ionic/angular-toolkit        : 5.0.3

Cordova:

   Cordova CLI       : not installed
   Cordova Platforms : not available
   Cordova Plugins   : not available

Utility:

   cordova-res : not installed globally
   native-run  : not installed globally

System:

   NodeJS : v16.13.0 (/home/tim/.nvm/versions/node/v16.13.0/bin/node)
   npm    : 8.7.0
   OS     : Linux 5.13

Little bit confused on how to use this

How do I actually use this in relation to an ionic capacitor app? Is there a tutorial on how to use it and write some practice tests? I tried to clone this and run it but was given an error

'TS_NODE_PROJECT' is not recognized as an internal or external command,
operable program or batch file.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] ionic-e2e:run:android: `TS_NODE_PROJECT=tests/tsconfig.json  wdio tests/config/wdio.android.config.ts`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] ionic-e2e:run:android script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

Modal / backdrop

His there a way to select elements in backdrop / modal ?

I'm struggling to to this (get 404)

[WD Proxy] Got response with status 404: {"value":{"error":"no such element","message":"no such element: Unable to locate element: {\"method\":\"css selector\",\"selector\":\"[data-testid=\"add-to-basket\"]\"}\n  (Session info: chrome=108.0.5359.128)","stacktrace":"0   chromedriver_mac64_v108.0.5359.71   0x00000001047c6f38 chromedriver_mac64_v108.0.5359.71 + 4910904\n1   chromedriver_mac64_v108.0.5359.71   0x0000000104746a03 chromedriver_mac64_v108.0.5359.71 + 4385283\n2   chromedriver_mac64_v108.0.5359.71   0x000000010438b747 chromedriver_mac64_v108.0.5359.71 + 472903\n3   chromedriver_mac64_v108.0.5359.71   0x00000001043d034c chromedriver_mac64_v108.0.5359.71 + 754508\n4   chromedriver_mac64_v108.0.5359.71   0x00000001043d05a1 chromedriver_mac64_v108.0.5359.71 + 755105\n5   chromedriver_mac64_v108.0.5359.71   0x00000001043c2216 chromedriver_mac64_v108.0.5359.71 + 696854\n6   chromedriver_mac64_v108.0.5359.71   0x00000001043f613d chromedriver_mac64_v108.0.5359.71 + 909629\n7   chromedriver_mac64_v108.0.5359.71   0x00000001043c20ed chromedriver_m..

Any plans for a new release?

I'm submitting a ... (check one with "x")
[ ] bug report
[x] feature request
[ ] support request => Please do not submit support requests here, use one of these channels: https://forum.ionicframework.com/ or http://ionicworldwide.herokuapp.com/

Could this example be updated for Ionic 7? For example, the way inputs are handled has changed (#11), and many of the dependencies are out of date. I'm trying to get this going in React based on the example by aaronksaunders, but I'm having trouble because I'm using more recent versions of the dependencies and there seem to be a lot of issues with the code.

Having a working v7 example would make debugging a lot easier for users who are trying to adapt this.

E2E-Tests: Ion-Input is broken on Ionic7 projects

I'm submitting a

  • bug report

Current behavior:
In e2e testing of ionic7 projects, the ion-input is not accessible / no value can be set.

Expected behavior:
Can set a value through the helpers class IonicInput

Steps to reproduce:
Try to use the helpers in an Ionic7 project.

In my opinion, you just have to look which changes the Ion-Input got from 6 โ‡พ 7 and adjust the class IonicInput accordingly. I assume other components did change as well, and maybe the repo ionic-e2e-example needs an entire upgrade to ionic7 as well. Maybe as a tag in repo to switch back to 6 any time.

Related code:

export class IonicInput extends IonicComponent {
constructor(selector: string) {
super(selector);
}
async setValue(
value: string,
{ visibilityTimeout = 5000 }: ElementActionOptions = {}
) {
const el = await Ionic$.$(this.selector as string);
await el.waitForDisplayed({ timeout: visibilityTimeout });
const ionTags = ['ion-input', 'ion-textarea'];
if (ionTags.indexOf(await el.getTagName()) >= 0) {
const input = await el.$('input,textarea');
await input.setValue(value);
} else {
return el.setValue(value);
}
}
async getValue({ visibilityTimeout = 5000 }: ElementActionOptions = {}) {
const el = await Ionic$.$(this.selector as string);
await el.waitForDisplayed({ timeout: visibilityTimeout });
const ionTags = ['ion-input', 'ion-textarea'];
if (ionTags.indexOf(await el.getTagName()) >= 0) {
const input = await el.$('input,textarea');
return input.getValue();
} else {
return el.getValue();
}
}
}

Other information:

Ionic info: (run ionic info from a terminal/cmd prompt and paste output below):

Ionic:

Ionic CLI : 7.1.1
Ionic Framework : @ionic/react 7.0.14

Capacitor:

Capacitor CLI : 5.0.5
@capacitor/android : 5.0.5
@capacitor/core : 5.0.5
@capacitor/ios : 5.0.5

Utility:

cordova-res : not installed globally
native-run : 1.7.2

System:

NodeJS : v19.7.0
npm : 9.5.0
OS : Linux 5.19

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.