test(router): migrate to playwright (#26406)

This commit is contained in:
Sean Perkins
2022-12-05 10:46:51 -05:00
committed by GitHub
parent 12a21b67b3
commit e32b9ce8f4
16 changed files with 407 additions and 597 deletions

View File

@ -1,10 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
test('router: basic', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/basic?ionic:_testing=true',
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@ -2,7 +2,7 @@
<html lang="en" dir="ltr"> <html lang="en" dir="ltr">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>Nav</title> <title>Router</title>
<meta <meta
name="viewport" name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"

View File

@ -1,12 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
test('redirect should support query string', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/basic#/redirect-to-three?ionic:_testing=true',
});
await page.waitForChanges();
const url = page.url();
expect(url).toContain('#/three?has_query_string=true');
});

View File

@ -1,29 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
test('push should support relative path', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/basic#/two/three/hola?ionic:_testing=true',
});
await page.waitForChanges();
const backButton = await page.$('#btn-rel');
await backButton?.click();
await page.waitForChanges();
const url = page.url();
expect(url).toContain('#/two/three/relative?param=1');
});
test('push should support absolute path', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/basic#/two/three/hola?ionic:_testing=true',
});
await page.waitForChanges();
const backButton = await page.$('#btn-abs');
await backButton?.click();
await page.waitForChanges();
const url = page.url();
expect(url).toContain('#/two/three/absolute');
});

View File

@ -0,0 +1,100 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('router: redirect', () => {
test.beforeEach(({ skip }) => {
skip.mode('md');
skip.rtl();
});
test('contains query parameters after redirect', async ({ page }) => {
await page.goto(`/src/components/router/test/basic#/redirect-to-three`);
expect(page.url()).toContain('#/three?has_query_string=true');
});
});
test.describe('router: push', () => {
test.beforeEach(({ skip }) => {
skip.mode('md');
skip.rtl();
});
test('should support relative path', async ({ page }) => {
await page.goto(`/src/components/router/test/basic#/two/three/hola`);
await page.click('#btn-rel');
expect(page.url()).toContain('#/two/three/relative?param=1');
});
test('should support absolute path', async ({ page }) => {
await page.goto(`/src/components/router/test/basic#/two/three/hola`);
await page.click('#btn-abs');
expect(page.url()).toContain('#/two/three/absolute');
});
});
test.describe('router: tabs', () => {
test.beforeEach(({ skip }) => {
skip.mode('md');
skip.rtl();
});
test('should activate the initial tab', async ({ page }) => {
await page.goto(`/src/components/router/test/basic`);
const tabOne = page.locator('tab-one');
await expect(tabOne).toBeVisible();
expect(page.url()).toContain('/basic?');
});
/**
* Selects the Schedule (tab two) tab and verifies that both the
* page is visible and the URL is correct.
*/
test('selecting a tab routes to the tab page', async ({ page }) => {
await page.goto(`/src/components/router/test/basic`);
const tabOne = page.locator('tab-one');
const tabTwo = page.locator('tab-two');
await page.click('#tab-button-tab-two');
await expect(tabOne).toBeHidden();
await expect(tabTwo).toBeVisible();
expect(page.url()).toContain('#/two');
});
test('should navigate to a nested page within a tab', async ({ page }) => {
await page.goto('/src/components/router/test/basic#/two');
const tabTwo = page.locator('tab-two');
const pageOne = page.locator('page-one');
await expect(tabTwo).toBeVisible();
await expect(pageOne).toBeVisible();
await page.click('text=Go to page 2');
const pageTwo = page.locator('page-two');
await expect(pageTwo).toBeVisible();
await expect(pageOne).toBeHidden();
await expect(page.url()).toContain('#/two/second-page');
});
test('navigating directly to a sub page should activate the page', async ({ page }) => {
await page.goto('/src/components/router/test/basic#/two/second-page');
const tabTwo = page.locator('tab-two');
const pageTwo = page.locator('page-two');
await expect(tabTwo).toBeVisible();
await expect(pageTwo).toBeVisible();
});
});

View File

@ -1,16 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
test('router: guards - guards should be run on initial load', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards#/guard-initial-page?ionic:_testing=true',
});
await page.waitForChanges();
await checkUrl(page, '#/child/1');
});
const checkUrl = async (page, url: string) => {
const getUrl = await page.url();
expect(getUrl).toContain(url);
};

View File

@ -1,125 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
test('router: guards - href - allow/allow', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards?ionic:_testing=true',
});
// Test 1: beforeEnter: allow, beforeLeave: allow
await setBeforeEnterHook(page, 'allow');
const href = await page.$('#href');
await href.click();
await page.waitForChanges();
await checkUrl(page, '#/child');
const backButton = await page.$('ion-back-button');
await backButton.click();
await page.waitForChanges();
await checkUrl(page, '#/home');
});
test('router: guards - href - block/allow', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards?ionic:_testing=true',
});
// Test 2: beforeEnter: block, beforeLeave: allow
await setBeforeEnterHook(page, 'block');
const href = await page.$('#href');
await href.click();
await page.waitForChanges();
await checkUrl(page, '#/home');
});
test('router: guards - href - redirect/allow', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards?ionic:_testing=true',
});
// Test 3: beforeEnter: redirect, beforeLeave: allow
await setBeforeEnterHook(page, 'redirect');
const href = await page.$('#href');
await href.click();
await page.waitForChanges();
await checkUrl(page, '#/test');
const backButton = await page.$('ion-back-button');
await backButton.click();
await page.waitForChanges();
await checkUrl(page, '#/home');
});
test('router: guards - href - allow/block', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards?ionic:_testing=true',
});
// Test 4: beforeEnter: allow, beforeLeave: block
await setBeforeLeaveHook(page, 'block');
const href = await page.$('#href');
await href.click();
await page.waitForChanges();
await checkUrl(page, '#/child');
const backButton = await page.$('ion-back-button');
await backButton.click();
await page.waitForChanges();
await checkUrl(page, '#/child');
});
// TODO this is an actual bug in the code.
test('router: guards - href - allow/redirect', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards?ionic:_testing=true',
});
// Test 5: beforeEnter: allow, beforeLeave: redirect
await setBeforeLeaveHook(page, 'redirect');
const href = await page.$('#href');
await href.click();
await page.waitForChanges();
await checkUrl(page, '#/child');
const backButton = await page.$('ion-back-button');
await backButton.click();
await page.waitForChanges();
await checkUrl(page, '#/test');
});
const checkUrl = async (page, url: string) => {
const getUrl = await page.url();
expect(getUrl).toContain(url);
};
const setBeforeEnterHook = async (page, type: string) => {
const button = await page.$(`ion-radio-group#beforeEnter ion-radio[value=${type}]`);
await button.click();
};
const setBeforeLeaveHook = async (page, type: string) => {
const button = await page.$(`ion-radio-group#beforeLeave ion-radio[value=${type}]`);
await button.click();
};

View File

@ -0,0 +1,90 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
import { setBeforeEnterHook, setBeforeLeaveHook } from '../test.utils';
test.describe('router: guards: href', () => {
test.beforeEach(async ({ page, skip }) => {
skip.mode('md');
skip.rtl();
await page.goto(`/src/components/router/test/guards`);
});
test('allow/allow', async ({ page }) => {
// beforeEnter: allow, beforeLeave: allow
await setBeforeEnterHook(page, 'allow');
await page.click('#href');
await page.waitForChanges();
expect(page.url()).toContain('#/child');
await page.click('ion-back-button');
await page.waitForChanges();
expect(page.url()).toContain('#/home');
});
test('block/allow', async ({ page }) => {
// beforeEnter: block, beforeLeave: allow
await setBeforeEnterHook(page, 'block');
await page.click('#href');
await page.waitForChanges();
expect(page.url()).toContain('#/home');
});
test('redirect/allow', async ({ page }) => {
// beforeEnter: redirect, beforeLeave: allow
await setBeforeEnterHook(page, 'redirect');
await page.click('#href');
await page.waitForChanges();
expect(page.url()).toContain('#/test');
await page.click('ion-back-button');
await page.waitForChanges();
expect(page.url()).toContain('#/home');
});
test('allow/bock', async ({ page }) => {
// beforeEnter: allow, beforeLeave: block
await setBeforeLeaveHook(page, 'block');
await page.click('#href');
await page.waitForChanges();
expect(page.url()).toContain('#/child');
await page.click('ion-back-button');
await page.waitForChanges();
expect(page.url()).toContain('#/child');
});
test('allow/redirect', async ({ page }) => {
// beforeEnter: allow, beforeLeave: redirect
await setBeforeLeaveHook(page, 'redirect');
await page.click('#href');
await page.waitForChanges();
expect(page.url()).toContain('#/child');
await page.click('ion-back-button');
await page.waitForChanges();
expect(page.url()).toContain('#/test');
});
});

View File

@ -1,124 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
test('router: guards - router-link - allow/allow', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards?ionic:_testing=true',
});
// Test 1: beforeEnter: allow, beforeLeave: allow
await setBeforeEnterHook(page, 'allow');
const routerLink = await page.$('#router-link');
await routerLink.click();
await page.waitForChanges();
await checkUrl(page, '#/child/1');
const backButton = await page.$('ion-back-button');
await backButton.click();
await page.waitForChanges();
await checkUrl(page, '#/home');
});
test('router: guards - router-link - block/allow', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards?ionic:_testing=true',
});
// Test 2: beforeEnter: block, beforeLeave: allow
await setBeforeEnterHook(page, 'block');
const routerLink = await page.$('#router-link');
await routerLink.click();
await page.waitForChanges();
await checkUrl(page, '#/home');
});
test('router: guards - router-link - redirect/allow', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards?ionic:_testing=true',
});
// Test 3: beforeEnter: redirect, beforeLeave: allow
await setBeforeEnterHook(page, 'redirect');
const routerLink = await page.$('#router-link');
await routerLink.click();
await page.waitForChanges();
await checkUrl(page, '#/test');
const backButton = await page.$('ion-back-button');
await backButton.click();
await page.waitForChanges();
await checkUrl(page, '#/home');
});
test('router: guards - router-link - allow/block', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards?ionic:_testing=true',
});
// Test 4: beforeEnter: allow, beforeLeave: block
await setBeforeLeaveHook(page, 'block');
const routerLink = await page.$('#router-link');
await routerLink.click();
await page.waitForChanges();
await checkUrl(page, '#/child/1');
const backButton = await page.$('ion-back-button');
await backButton.click();
await page.waitForChanges();
await checkUrl(page, '#/child/1');
});
test('router: guards - router-link - allow/redirect', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards?ionic:_testing=true',
});
// Test 5: beforeEnter: allow, beforeLeave: redirect
await setBeforeLeaveHook(page, 'redirect');
const routerLink = await page.$('#router-link');
await routerLink.click();
await page.waitForChanges();
await checkUrl(page, '#/child/1');
const backButton = await page.$('ion-back-button');
await backButton.click();
await page.waitForChanges();
await checkUrl(page, '#/test');
});
const checkUrl = async (page, url: string) => {
const getUrl = await page.url();
expect(getUrl).toContain(url);
};
const setBeforeEnterHook = async (page, type: string) => {
const button = await page.$(`ion-radio-group#beforeEnter ion-radio[value=${type}]`);
await button.click();
};
const setBeforeLeaveHook = async (page, type: string) => {
const button = await page.$(`ion-radio-group#beforeLeave ion-radio[value=${type}]`);
await button.click();
};

View File

@ -0,0 +1,92 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
import { setBeforeEnterHook, setBeforeLeaveHook } from '../test.utils';
test.describe('router: guards: router-link', () => {
test.beforeEach(async ({ page, skip }) => {
skip.mode('md');
skip.rtl();
await page.goto(`/src/components/router/test/guards`);
});
test('allow/allow', async ({ page }) => {
// beforeEnter: allow, beforeLeave: allow
await setBeforeEnterHook(page, 'allow');
await page.click('#router-link');
await page.waitForChanges();
expect(page.url()).toContain('#/child/1');
await page.click('ion-back-button');
await page.waitForChanges();
expect(page.url()).toContain('#/home');
});
test('block/allow', async ({ page }) => {
// beforeEnter: block, beforeLeave: allow
await setBeforeEnterHook(page, 'block');
await page.click('#router-link');
await page.waitForChanges();
expect(page.url()).toContain('#/home');
});
test('redirect/allow', async ({ page }) => {
// beforeEnter: redirect, beforeLeave: allow
await setBeforeEnterHook(page, 'redirect');
await page.click('#router-link');
await page.waitForChanges();
expect(page.url()).toContain('#/test');
await page.click('ion-back-button');
await page.waitForChanges();
expect(page.url()).toContain('#/home');
});
test('allow/block', async ({ page }) => {
// beforeEnter: allow, beforeLeave: block
await setBeforeLeaveHook(page, 'block');
await page.click('#router-link');
await page.waitForChanges();
expect(page.url()).toContain('#/child/1');
await page.click('ion-back-button');
await page.waitForChanges();
expect(page.url()).toContain('#/child/1');
});
test('allow/redirect', async ({ page }) => {
// beforeEnter: allow, beforeLeave: redirect
await setBeforeLeaveHook(page, 'redirect');
await page.click('#router-link');
await page.waitForChanges();
expect(page.url()).toContain('#/child/1');
await page.click('ion-back-button');
await page.waitForChanges();
expect(page.url()).toContain('#/test');
});
});

View File

@ -1,124 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
test('router: guards - router.push - allow/allow', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards?ionic:_testing=true',
});
// Test 1: beforeEnter: allow, beforeLeave: allow
await setBeforeEnterHook(page, 'allow');
const routerPush = await page.$('#router-push');
await routerPush.click();
await page.waitForChanges();
await checkUrl(page, '#/child');
const backButton = await page.$('ion-back-button');
await backButton.click();
await page.waitForChanges();
await checkUrl(page, '#/home');
});
test('router: guards - router.push - block/allow', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards?ionic:_testing=true',
});
// Test 2: beforeEnter: block, beforeLeave: allow
await setBeforeEnterHook(page, 'block');
const routerPush = await page.$('#router-push');
await routerPush.click();
await page.waitForChanges();
await checkUrl(page, '#/home');
});
test('router: guards - router.push - redirect/allow', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards?ionic:_testing=true',
});
// Test 3: beforeEnter: redirect, beforeLeave: allow
await setBeforeEnterHook(page, 'redirect');
const routerPush = await page.$('#router-push');
await routerPush.click();
await page.waitForChanges();
await checkUrl(page, '#/test');
const backButton = await page.$('ion-back-button');
await backButton.click();
await page.waitForChanges();
await checkUrl(page, '#/home');
});
test('router: guards - router.push - allow/block', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards?ionic:_testing=true',
});
// Test 4: beforeEnter: allow, beforeLeave: block
await setBeforeLeaveHook(page, 'block');
const routerPush = await page.$('#router-push');
await routerPush.click();
await page.waitForChanges();
await checkUrl(page, '#/child');
const backButton = await page.$('ion-back-button');
await backButton.click();
await page.waitForChanges();
await checkUrl(page, '#/child');
});
test('router: guards - router.push - allow/redirect', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/guards?ionic:_testing=true',
});
// Test 5: beforeEnter: allow, beforeLeave: redirect
await setBeforeLeaveHook(page, 'redirect');
const routerPush = await page.$('#router-push');
await routerPush.click();
await page.waitForChanges();
await checkUrl(page, '#/child');
const backButton = await page.$('ion-back-button');
await backButton.click();
await page.waitForChanges();
await checkUrl(page, '#/test');
});
const checkUrl = async (page, url: string) => {
const getUrl = await page.url();
expect(getUrl).toContain(url);
};
const setBeforeEnterHook = async (page, type: string) => {
const button = await page.$(`ion-radio-group#beforeEnter ion-radio[value=${type}]`);
await button.click();
};
const setBeforeLeaveHook = async (page, type: string) => {
const button = await page.$(`ion-radio-group#beforeLeave ion-radio[value=${type}]`);
await button.click();
};

View File

@ -0,0 +1,92 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
import { setBeforeEnterHook, setBeforeLeaveHook } from '../test.utils';
test.describe('router: guards: router.push', () => {
test.beforeEach(async ({ page, skip }) => {
skip.mode('md');
skip.rtl();
await page.goto(`/src/components/router/test/guards`);
});
test('allow/allow', async ({ page }) => {
// beforeEnter: allow, beforeLeave: allow
await setBeforeEnterHook(page, 'allow');
await page.click('#router-push');
await page.waitForChanges();
expect(page.url()).toContain('#/child');
await page.click('ion-back-button');
await page.waitForChanges();
expect(page.url()).toContain('#/home');
});
test('block/allow', async ({ page }) => {
// beforeEnter: block, beforeLeave: allow
await setBeforeEnterHook(page, 'block');
await page.click('#router-push');
await page.waitForChanges();
expect(page.url()).toContain('#/home');
});
test('redirect/allow', async ({ page }) => {
// beforeEnter: redirect, beforeLeave: allow
await setBeforeEnterHook(page, 'redirect');
await page.click('#router-push');
await page.waitForChanges();
expect(page.url()).toContain('#/test');
await page.click('ion-back-button');
await page.waitForChanges();
expect(page.url()).toContain('#/home');
});
test('allow/block', async ({ page }) => {
// beforeEnter: allow, beforeLeave: block
await setBeforeLeaveHook(page, 'block');
await page.click('#router-push');
await page.waitForChanges();
expect(page.url()).toContain('#/child');
await page.click('ion-back-button');
await page.waitForChanges();
expect(page.url()).toContain('#/child');
});
test('allow/redirect', async ({ page }) => {
// beforeEnter: allow, beforeLeave: redirect
await setBeforeLeaveHook(page, 'redirect');
await page.click('#router-push');
await page.waitForChanges();
expect(page.url()).toContain('#/child');
await page.click('ion-back-button');
await page.waitForChanges();
expect(page.url()).toContain('#/test');
});
});

View File

@ -0,0 +1,15 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('router: guards', () => {
test.beforeEach(({ skip }) => {
skip.mode('md');
skip.rtl();
});
test('guards should be run on initial load', async ({ page }) => {
await page.goto(`/src/components/router/test/guards#/guard-initial-page`);
expect(page.url()).toContain('#/child/1');
});
});

View File

@ -0,0 +1,17 @@
import type { Page } from '@playwright/test';
/**
* Selects a radio button from the radio group for configuring the
* beforeEnter hook of the router.
*/
export const setBeforeEnterHook = async (page: Page, type: string) => {
await page.click(`ion-radio-group#beforeEnter ion-radio[value=${type}]`);
};
/**
* Selects a radio button from the radio group for configuring the
* beforeLeave hook of the router.
*/
export const setBeforeLeaveHook = async (page: Page, type: string) => {
await page.click(`ion-radio-group#beforeLeave ion-radio[value=${type}]`);
};

View File

@ -1,10 +0,0 @@
import { newE2EPage } from '@stencil/core/testing';
test('router: tabs', async () => {
const page = await newE2EPage({
url: '/src/components/router/test/tabs?ionic:_testing=true',
});
const compare = await page.compareScreenshot();
expect(compare).toMatchScreenshot();
});

View File

@ -1,146 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Nav</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<script>
class PageTabOne extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-title>Tab One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">Tab One</ion-title>
</ion-toolbar>
</ion-header>
<div class="ion-padding">
<ion-button href="#/second-page">Go to Child</ion-button>
</div>
</ion-content>`;
}
}
class PageTabTwo extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-title>Tab Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">Tab Two</ion-title>
</ion-toolbar>
</ion-header>
<div class="ion-padding">
<ion-button href="#/second-page">Go to Child</ion-button>
</div>
</ion-content>`;
}
}
class PageTwo extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons>
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Child</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">Child</ion-title>
</ion-toolbar>
</ion-header>
</ion-content>`;
}
}
class TabOne extends HTMLElement {
connectedCallback() {
this.innerHTML = `<ion-nav></ion-nav>`;
}
}
class TabTwo extends HTMLElement {
connectedCallback() {
this.innerHTML = `<ion-nav></ion-nav>`;
}
}
class TabsPage extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-tabs>
<ion-tab tab="tab-one" component="tab-one"></ion-tab>
<ion-tab tab="tab-two" component="tab-two"></ion-tab>
<ion-tab-bar slot="bottom">
<ion-tab-button tab="tab-one">
<ion-label>Tab One</ion-label>
<ion-icon name="star"></ion-icon>
</ion-tab-button>
<ion-tab-button tab="tab-two">
<ion-label>Tab Two</ion-label>
<ion-icon name="globe"></ion-icon>
</ion-tab-button>
</ion-tab-bar>
</ion-tabs>
`;
}
}
customElements.define('page-tab-one', PageTabOne);
customElements.define('page-tab-two', PageTabTwo);
customElements.define('page-two', PageTwo);
customElements.define('tabs-page', TabsPage);
customElements.define('tab-one', TabOne);
customElements.define('tab-two', TabTwo);
</script>
</head>
<body>
<ion-app>
<ion-router>
<ion-route url="/" component="tabs-page">
<ion-route url="/one" component="tab-one">
<ion-route component="page-tab-one"></ion-route>
</ion-route>
<ion-route url="/two" component="tab-two">
<ion-route component="page-tab-two"></ion-route>
</ion-route>
</ion-route>
<ion-route url="/second-page" component="page-two"></ion-route>
</ion-router>
<ion-nav></ion-nav>
</ion-app>
</body>
</html>