diff --git a/.circleci/config.yml b/.circleci/config.yml index adc58e6f77cf..969ed7de62dd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -130,18 +130,17 @@ jobs: - run: bazel test tools/public_api_guard/... - # ---------------------------------------------------------------- - # Job that runs the e2e tests with Protractor and Chrome Headless - # ---------------------------------------------------------------- + # ----------------------------------------------------------------- + # Job that runs the e2e tests with Protractor and Chromium headless + # ----------------------------------------------------------------- e2e_tests: <<: *job_defaults resource_class: xlarge steps: - *checkout_code - *restore_cache - - *yarn_install - - run: yarn gulp ci:e2e + - run: bazel test e2e/... # ------------------------------------------------------------------------------------------ # Job that runs the unit tests on locally installed browsers (Chrome and Firefox headless). diff --git a/WORKSPACE b/WORKSPACE index 84bca943fec8..e14f26958f89 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -12,8 +12,10 @@ http_archive( # Add TypeScript rules http_archive( name = "build_bazel_rules_typescript", - url = "https://github.com/bazelbuild/rules_typescript/archive/0.22.0.zip", - strip_prefix = "rules_typescript-0.22.0", + # Explicitly depend on https://github.com/bazelbuild/rules_typescript/pull/327 which fixes the devserver + # for windows. Once this has been reviewed and merged, we can switch back to a normal release. + url = "https://github.com/bazelbuild/rules_typescript/archive/1bb017e2f9c58f96bac8ddda2ed4a170282bc58e.zip", + strip_prefix = "rules_typescript-1bb017e2f9c58f96bac8ddda2ed4a170282bc58e", ) # Add Angular source and Bazel rules. diff --git a/e2e/BUILD.bazel b/e2e/BUILD.bazel new file mode 100644 index 000000000000..9fc232582cf9 --- /dev/null +++ b/e2e/BUILD.bazel @@ -0,0 +1,30 @@ +package(default_visibility=["//visibility:public"]) + +load("@angular//:index.bzl", "protractor_web_test_suite") +load("//tools:defaults.bzl", "ts_library") + +ts_library( + name = "e2e_specs_lib", + srcs = glob(["**/*.ts"]), + tsconfig = ":tsconfig.json", + deps = [ + "@matdeps//@types/jasmine", + "@matdeps//protractor" + ] +) + +protractor_web_test_suite( + name = "e2e", + configuration = ":protractor.conf.js", + on_prepare = ":start-devserver.js", + server = "//src/e2e-app:devserver", + deps = [ + "@matdeps//protractor", + ":e2e_specs_lib" + ], + data = [ + "@angular//packages/bazel/src/protractor/utils", + "//tools/axe-protractor", + ], +) + diff --git a/e2e/components/block-scroll-strategy-e2e.spec.ts b/e2e/components/block-scroll-strategy-e2e.spec.ts index d00990893604..5d80f7bcc74f 100644 --- a/e2e/components/block-scroll-strategy-e2e.spec.ts +++ b/e2e/components/block-scroll-strategy-e2e.spec.ts @@ -7,44 +7,44 @@ describe('scroll blocking', () => { afterEach(() => clickOn('disable')); it('should not be able to scroll programmatically along the x axis', async () => { - scrollPage(0, 100); + await scrollPage(0, 100); expect((await getScrollPosition()).y).toBe(100, 'Expected the page to be scrollable.'); - clickOn('enable'); - scrollPage(0, 200); + await clickOn('enable'); + await scrollPage(0, 200); expect((await getScrollPosition()).y).toBe(100, 'Expected the page not to be scrollable.'); - clickOn('disable'); - scrollPage(0, 300); + await clickOn('disable'); + await scrollPage(0, 300); expect((await getScrollPosition()).y).toBe(300, 'Exected page to be scrollable again.'); }); it('should not be able to scroll programmatically along the y axis', async () => { - scrollPage(100, 0); + await scrollPage(100, 0); expect((await getScrollPosition()).x).toBe(100, 'Expected the page to be scrollable.'); - clickOn('enable'); - scrollPage(200, 0); + await clickOn('enable'); + await scrollPage(200, 0); expect((await getScrollPosition()).x).toBe(100, 'Expected the page not to be scrollable.'); - clickOn('disable'); - scrollPage(300, 0); + await clickOn('disable'); + await scrollPage(300, 0); expect((await getScrollPosition()).x).toBe(300, 'Exected page to be scrollable again.'); }); it('should not be able to scroll via the keyboard along the y axis', async () => { const body = element(by.tagName('body')); - scrollPage(0, 100); + await scrollPage(0, 100); expect((await getScrollPosition()).y).toBe(100, 'Expected the page to be scrollable.'); - clickOn('enable'); + await clickOn('enable'); await body.sendKeys(Key.ARROW_DOWN); await body.sendKeys(Key.ARROW_DOWN); await body.sendKeys(Key.ARROW_DOWN); expect((await getScrollPosition()).y).toBe(100, 'Expected the page not to be scrollable.'); - clickOn('disable'); + await clickOn('disable'); await body.sendKeys(Key.ARROW_DOWN); await body.sendKeys(Key.ARROW_DOWN); await body.sendKeys(Key.ARROW_DOWN); @@ -55,16 +55,16 @@ describe('scroll blocking', () => { it('should not be able to scroll via the keyboard along the x axis', async () => { const body = element(by.tagName('body')); - scrollPage(100, 0); + await scrollPage(100, 0); expect((await getScrollPosition()).x).toBe(100, 'Expected the page to be scrollable.'); - clickOn('enable'); + await clickOn('enable'); await body.sendKeys(Key.ARROW_RIGHT); await body.sendKeys(Key.ARROW_RIGHT); await body.sendKeys(Key.ARROW_RIGHT); expect((await getScrollPosition()).x).toBe(100, 'Expected the page not to be scrollable.'); - clickOn('disable'); + await clickOn('disable'); await body.sendKeys(Key.ARROW_RIGHT); await body.sendKeys(Key.ARROW_RIGHT); await body.sendKeys(Key.ARROW_RIGHT); @@ -76,14 +76,15 @@ describe('scroll blocking', () => { async () => { const scroller = element(by.id('scroller')); - browser.executeScript(`document.getElementById('scroller').scrollTop = 200;`); - scrollPage(0, 100); + await browser.executeScript(`document.getElementById('scroller').scrollTop = 200;`); + await scrollPage(0, 100); expect((await getScrollPosition()).y).toBe(100, 'Expected the page to be scrollable.'); - clickOn('enable'); - scroller.sendKeys(Key.ARROW_DOWN); - scroller.sendKeys(Key.ARROW_DOWN); - scroller.sendKeys(Key.ARROW_DOWN); + await clickOn('enable'); + await scroller.sendKeys(Key.ARROW_DOWN); + await scroller.sendKeys(Key.ARROW_DOWN); + await scroller.sendKeys(Key.ARROW_DOWN); + expect((await getScrollPosition()).y).toBe(100, 'Expected the page not to have scrolled.'); }); @@ -91,25 +92,26 @@ describe('scroll blocking', () => { async () => { const scroller = element(by.id('scroller')); - browser.executeScript(`document.getElementById('scroller').scrollLeft = 200;`); - scrollPage(100, 0); + await browser.executeScript(`document.getElementById('scroller').scrollLeft = 200;`); + await scrollPage(100, 0); expect((await getScrollPosition()).x).toBe(100, 'Expected the page to be scrollable.'); - clickOn('enable'); - scroller.sendKeys(Key.ARROW_RIGHT); - scroller.sendKeys(Key.ARROW_RIGHT); - scroller.sendKeys(Key.ARROW_RIGHT); + await clickOn('enable'); + await scroller.sendKeys(Key.ARROW_RIGHT); + await scroller.sendKeys(Key.ARROW_RIGHT); + await scroller.sendKeys(Key.ARROW_RIGHT); + expect((await getScrollPosition()).x).toBe(100, 'Expected the page not to have scrolled.'); }); }); // Clicks on a button programmatically. Note that we can't use Protractor's `.click`, because // it performs a real click, which will scroll the button into view. -function clickOn(id: string) { - browser.executeScript(`document.getElementById('${id}').click()`); +async function clickOn(id: string) { + await browser.executeScript(`document.getElementById('${id}').click()`); } // Scrolls the page to the specified coordinates. -function scrollPage(x: number, y: number) { - return browser.executeScript(`window.scrollTo(${x}, ${y});`); +async function scrollPage(x: number, y: number) { + await browser.executeScript(`window.scrollTo(${x}, ${y});`); } diff --git a/e2e/components/button-e2e.spec.ts b/e2e/components/button-e2e.spec.ts index b45928928acf..ad9ba4b469a0 100644 --- a/e2e/components/button-e2e.spec.ts +++ b/e2e/components/button-e2e.spec.ts @@ -1,18 +1,20 @@ import {browser, by, element, ExpectedConditions} from 'protractor'; describe('button', () => { + describe('disabling behavior', () => { - beforeEach(() => browser.get('/button')); + + beforeEach(async () => await browser.get('/button')); it('should prevent click handlers from executing when disabled', async () => { - element(by.id('test-button')).click(); + await element(by.id('test-button')).click(); expect(await element(by.id('click-counter')).getText()).toEqual('1'); await browser.wait(ExpectedConditions.not( ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))); - element(by.id('disable-toggle')).click(); - element(by.id('test-button')).click(); + await element(by.id('disable-toggle')).click(); + await element(by.id('test-button')).click(); expect(await element(by.id('click-counter')).getText()).toEqual('1'); await browser.wait(ExpectedConditions.not( diff --git a/e2e/components/button-toggle-e2e.spec.ts b/e2e/components/button-toggle-e2e.spec.ts index 32da86a70c12..cbd09db3f341 100644 --- a/e2e/components/button-toggle-e2e.spec.ts +++ b/e2e/components/button-toggle-e2e.spec.ts @@ -2,10 +2,10 @@ import {browser, by, element} from 'protractor'; describe('button-toggle', () => { - beforeEach(() => browser.get('/button-toggle')); + beforeEach(async () => await browser.get('/button-toggle')); it('should show a button-toggle', async () => { - expect(element(by.tagName('mat-button-toggle'))).toBeDefined(); + expect(await element(by.tagName('mat-button-toggle'))).toBeDefined(); }); }); diff --git a/e2e/components/card-e2e.spec.ts b/e2e/components/card-e2e.spec.ts index 49a8b6bffd4c..943978838c2e 100644 --- a/e2e/components/card-e2e.spec.ts +++ b/e2e/components/card-e2e.spec.ts @@ -2,11 +2,10 @@ import {browser, by, element} from 'protractor'; describe('mat-card', () => { - beforeEach(() => browser.get('/cards')); + beforeEach(async () => await browser.get('/cards')); it('should show a card', async () => { - const card = element(by.tagName('mat-card')); - expect(card).toBeDefined(); + expect(await element(by.tagName('mat-card'))).toBeDefined(); }); }); diff --git a/e2e/components/checkbox-e2e.spec.ts b/e2e/components/checkbox-e2e.spec.ts index 44af27cbcae2..c8a6fcf92ca7 100644 --- a/e2e/components/checkbox-e2e.spec.ts +++ b/e2e/components/checkbox-e2e.spec.ts @@ -3,31 +3,31 @@ import {browser, by, element, Key} from 'protractor'; describe('checkbox', () => { describe('check behavior', () => { - beforeEach(() => browser.get('/checkbox')); + beforeEach(async () => await browser.get('/checkbox')); it('should be checked when clicked, and unchecked when clicked again', async () => { - let checkboxEl = element(by.id('test-checkbox')); - let inputEl = element(by.css('input[id=test-checkbox-input]')); + const checkboxEl = element(by.id('test-checkbox')); + const inputEl = element(by.css('input[id=test-checkbox-input]')); - checkboxEl.click(); + await checkboxEl.click(); - expect(inputEl.getAttribute('checked')) + expect(await inputEl.getAttribute('checked')) .toBeTruthy('Expect checkbox "checked" property to be true'); - checkboxEl.click(); + await checkboxEl.click(); - expect(inputEl.getAttribute('checked')) + expect(await inputEl.getAttribute('checked')) .toBeFalsy('Expect checkbox "checked" property to be false'); }); - it('should toggle the checkbox when pressing space', () => { - let inputEl = element(by.css('input[id=test-checkbox-input]')); + it('should toggle the checkbox when pressing space', async () => { + const inputEl = element(by.css('input[id=test-checkbox-input]')); - expect(inputEl.getAttribute('checked')) + expect(await inputEl.getAttribute('checked')) .toBeFalsy('Expect checkbox "checked" property to be false'); - inputEl.sendKeys(Key.SPACE); + await inputEl.sendKeys(Key.SPACE); - expect(inputEl.getAttribute('checked')) + expect(await inputEl.getAttribute('checked')) .toBeTruthy('Expect checkbox "checked" property to be true'); }); }); diff --git a/e2e/components/dialog-e2e.spec.ts b/e2e/components/dialog-e2e.spec.ts index 31caf262695a..1f8bac8c6f8c 100644 --- a/e2e/components/dialog-e2e.spec.ts +++ b/e2e/components/dialog-e2e.spec.ts @@ -8,99 +8,99 @@ import { } from '../util/index'; describe('dialog', () => { - beforeEach(() => browser.get('/dialog')); + beforeEach(async () => await browser.get('/dialog')); - it('should open a dialog', () => { - element(by.id('default')).click(); - expectToExist('mat-dialog-container'); + it('should open a dialog', async () => { + await element(by.id('default')).click(); + await expectToExist('mat-dialog-container'); }); - it('should open a template dialog', () => { - expectToExist('.my-template-dialog', false); - element(by.id('template')).click(); - expectToExist('.my-template-dialog'); + it('should open a template dialog', async () => { + await expectToExist('.my-template-dialog', false); + await element(by.id('template')).click(); + await expectToExist('.my-template-dialog'); }); it('should close by clicking on the backdrop', async() => { - element(by.id('default')).click(); + await element(by.id('default')).click(); await waitForDialog(); - clickOnBackrop(); - expectToExist('mat-dialog-container', false); + await clickOnBackdrop(); + await expectToExist('mat-dialog-container', false); }); it('should close by pressing escape', async () => { - element(by.id('default')).click(); + await element(by.id('default')).click(); await waitForDialog(); - pressKeys(Key.ESCAPE); - expectToExist('mat-dialog-container', false); + await pressKeys(Key.ESCAPE); + await expectToExist('mat-dialog-container', false); }); it('should close by pressing escape when the first tabbable element has lost focus', async () => { - element(by.id('default')).click(); + await element(by.id('default')).click(); await waitForDialog(); - clickElementAtPoint('mat-dialog-container', { x: 0, y: 0 }); - pressKeys(Key.ESCAPE); - expectToExist('mat-dialog-container', false); + await clickElementAtPoint('mat-dialog-container', { x: 0, y: 0 }); + await pressKeys(Key.ESCAPE); + await expectToExist('mat-dialog-container', false); }); it('should close by clicking on the "close" button', async () => { - element(by.id('default')).click(); + await element(by.id('default')).click(); await waitForDialog(); - element(by.id('close')).click(); - expectToExist('mat-dialog-container', false); + await element(by.id('close')).click(); + await expectToExist('mat-dialog-container', false); }); it('should focus the first focusable element', async () => { - element(by.id('default')).click(); + await element(by.id('default')).click(); await waitForDialog(); - expectFocusOn('mat-dialog-container input'); + await expectFocusOn('mat-dialog-container input'); }); it('should restore focus to the element that opened the dialog', async () => { - let openButton = element(by.id('default')); + const openButton = element(by.id('default')); - openButton.click(); + await openButton.click(); await waitForDialog(); - clickOnBackrop(); - expectFocusOn(openButton); + await clickOnBackdrop(); + await expectFocusOn(openButton); }); it('should prevent tabbing out of the dialog', async () => { - element(by.id('default')).click(); + await element(by.id('default')).click(); await waitForDialog(); - pressKeys(Key.TAB, Key.TAB, Key.TAB); - expectFocusOn('#close'); + await pressKeys(Key.TAB, Key.TAB, Key.TAB); + await expectFocusOn('#close'); }); it('should be able to prevent closing by clicking on the backdrop', async () => { - element(by.id('disabled')).click(); + await element(by.id('disabled')).click(); await waitForDialog(); - clickOnBackrop(); - expectToExist('mat-dialog-container'); + await clickOnBackdrop(); + await expectToExist('mat-dialog-container'); }); it('should be able to prevent closing by pressing escape', async () => { - element(by.id('disabled')).click(); + await element(by.id('disabled')).click(); await waitForDialog(); - pressKeys(Key.ESCAPE); - expectToExist('mat-dialog-container'); + await pressKeys(Key.ESCAPE); + await expectToExist('mat-dialog-container'); }); - function waitForDialog() { - return waitForElement('mat-dialog-container'); + async function waitForDialog() { + await waitForElement('mat-dialog-container'); } - function clickOnBackrop() { - clickElementAtPoint('.cdk-overlay-backdrop', { x: 0, y: 0 }); + async function clickOnBackdrop() { + await clickElementAtPoint('.cdk-overlay-backdrop', { x: 0, y: 0 }); } }); diff --git a/e2e/components/expansion-e2e.spec.ts b/e2e/components/expansion-e2e.spec.ts index dd7203c5025d..b7d78ecbab9f 100644 --- a/e2e/components/expansion-e2e.spec.ts +++ b/e2e/components/expansion-e2e.spec.ts @@ -2,10 +2,10 @@ import {browser, by, element} from 'protractor'; describe('expansion', () => { - beforeEach(() => browser.get('/expansion')); + beforeEach(async () => await browser.get('/expansion')); it('should show an accordion', async () => { - expect(element(by.css('.mat-accordion'))).toBeDefined(); + expect(await element(by.css('.mat-accordion'))).toBeDefined(); }); it('should show two panels', async () => { @@ -18,7 +18,7 @@ describe('expansion', () => { expect(await panelContent.isDisplayed()).toBe(false); - panelHeader.click(); + await panelHeader.click(); expect(await panelContent.isDisplayed()).toBe(true); }); @@ -28,13 +28,13 @@ describe('expansion', () => { const panelDescription = element .all(by.css('.mat-expansion-panel-header mat-panel-description')).get(1); - panelHeader.click(); + await panelHeader.click(); - expect(panelDescription.getText()).toContain('Currently I am open'); + expect(await panelDescription.getText()).toContain('Currently I am open'); - panelHeader.click(); + await panelHeader.click(); - expect(panelDescription.getText()).toContain('Currently I am closed'); + expect(await panelDescription.getText()).toContain('Currently I am closed'); }); }); diff --git a/e2e/components/grid-list-e2e.spec.ts b/e2e/components/grid-list-e2e.spec.ts index 81f7f36476bc..f0cec6f60e50 100644 --- a/e2e/components/grid-list-e2e.spec.ts +++ b/e2e/components/grid-list-e2e.spec.ts @@ -2,13 +2,13 @@ import {browser} from 'protractor'; import {expectToExist} from '../util/index'; describe('grid-list', () => { - beforeEach(() => browser.get('/grid-list')); + beforeEach(async () => await browser.get('/grid-list')); - it('should render a grid list container', () => { - expectToExist('mat-grid-list'); + it('should render a grid list container', async () => { + await expectToExist('mat-grid-list'); }); - it('should render list items inside the grid list container', () => { - expectToExist('mat-grid-list mat-grid-tile'); + it('should render list items inside the grid list container', async () => { + await expectToExist('mat-grid-list mat-grid-tile'); }); }); diff --git a/e2e/components/icon-e2e.spec.ts b/e2e/components/icon-e2e.spec.ts index f0261f5d2d85..52eb97d63e01 100644 --- a/e2e/components/icon-e2e.spec.ts +++ b/e2e/components/icon-e2e.spec.ts @@ -5,8 +5,8 @@ describe('icon', () => { describe('font icons by ligature', () => { let testIcon: any; - beforeEach(() => { - browser.get('/icon'); + beforeEach(async () => { + await browser.get('/icon'); testIcon = element(by.id('test-icon')); }); @@ -17,8 +17,8 @@ describe('icon', () => { expect(attr).toContain('material-icons'); }); - it('should have the correct role when used', () => { - expect(testIcon.getAttribute('role')).toBe('img'); + it('should have the correct role when used', async () => { + expect(await testIcon.getAttribute('role')).toBe('img'); }); }); }); diff --git a/e2e/components/input-e2e.spec.ts b/e2e/components/input-e2e.spec.ts index efefad4dedb0..4bb13e84c419 100644 --- a/e2e/components/input-e2e.spec.ts +++ b/e2e/components/input-e2e.spec.ts @@ -1,69 +1,69 @@ import {browser, by, element} from 'protractor'; describe('input', () => { + + beforeEach(async () => await browser.get('/input')); + describe('text input', () => { - beforeEach(() => browser.get('/input')); it('should update input value when user types', async () => { - let input = element(by.id('text-input')); - input.sendKeys('abc123'); + const input = element(by.id('text-input')); + await input.sendKeys('abc123'); expect(await input.getAttribute('value')).toBe('abc123'); }); }); describe('number input', () => { - beforeEach(() => browser.get('/input')); it('should update input value when user types', async () => { - let input = element(by.id('number-input')); - input.sendKeys('abc123'); + const input = element(by.id('number-input')); + await input.sendKeys('abc123'); expect(await input.getAttribute('value')).toBe('123'); }); it('should increment when increment button clicked', async () => { const input = element(by.id('number-input')); - - input.click(); + await input.click(); const size = await input.getSize(); - browser.actions() - .mouseMove(input, {x: size.width - 5, y: 5}) - .click() - .perform(); + await browser.actions() + .mouseMove(input, {x: size.width - 5, y: 5}) + .perform(); + // Workaround: https://github.com/angular/protractor/issues/4578 + await browser.actions().click().perform(); expect(await input.getAttribute('value')).toBe('1'); - browser.actions() + await browser.actions() .mouseMove(input, {x: size.width - 5, y: size.height - 5}) - .click() .perform(); + // Workaround: https://github.com/angular/protractor/issues/4578 + await browser.actions().click().perform(); expect(await input.getAttribute('value')).toBe('0'); }); }); describe('textarea', () => { - beforeEach(() => browser.get('/input')); it('should update input value when user types', async () => { - let input = element(by.id('text-area')); - input.sendKeys('abc123'); + const input = element(by.id('text-area')); + await input.sendKeys('abc123'); expect(await input.getAttribute('value')).toBe('abc123'); }); }); describe('autosize-textarea', () => { - beforeEach(() => browser.get('/input')); - it('should resize correctly', () => { - let input = element(by.id('autosize-text-area')); - input.sendKeys('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); + it('should resize correctly', async () => { + const input = element(by.id('autosize-text-area')); + await input.sendKeys('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); }); - it('should enfore max rows', () => { - let input = element(by.id('autosize-text-area')); - input.sendKeys( + it('should enfore max rows', async () => { + const input = element(by.id('autosize-text-area')); + await input.sendKeys( 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + diff --git a/e2e/components/list-e2e.spec.ts b/e2e/components/list-e2e.spec.ts index 6b86b5be1df9..5d6881d42ada 100644 --- a/e2e/components/list-e2e.spec.ts +++ b/e2e/components/list-e2e.spec.ts @@ -2,13 +2,13 @@ import {browser} from 'protractor'; import {expectToExist} from '../util/index'; describe('list', () => { - beforeEach(() => browser.get('/list')); + beforeEach(async () => await browser.get('/list')); - it('should render a list container', () => { - expectToExist('mat-list'); + it('should render a list container', async () => { + await expectToExist('mat-list'); }); - it('should render list items inside the list container', () => { - expectToExist('mat-list mat-list-item'); + it('should render list items inside the list container', async () => { + await expectToExist('mat-list mat-list-item'); }); }); diff --git a/e2e/components/menu-e2e.spec.ts b/e2e/components/menu-e2e.spec.ts index b0516492f09b..e8c91ceb0dff 100644 --- a/e2e/components/menu-e2e.spec.ts +++ b/e2e/components/menu-e2e.spec.ts @@ -1,9 +1,9 @@ -import {Key, protractor, browser, by, element, ExpectedConditions} from 'protractor'; +import {browser, by, element, ExpectedConditions, Key, protractor} from 'protractor'; import { - expectToExist, expectAlignedWith, expectFocusOn, expectLocation, + expectToExist, pressKeys, } from '../util/index'; @@ -13,136 +13,151 @@ const not = ExpectedConditions.not; describe('menu', () => { const menuSelector = '.mat-menu-panel'; - let page: MenuPage; - - beforeEach(() => page = new MenuPage()); + const page = { + menu: () => element(by.css('.mat-menu-panel')), + start: () => element(by.id('start')), + trigger: () => element(by.id('trigger')), + triggerTwo: () => element(by.id('trigger-two')), + backdrop: () => element(by.css('.cdk-overlay-backdrop')), + items: (index: number) => element.all(by.css('[mat-menu-item]')).get(index), + textArea: () => element(by.id('text')), + beforeTrigger: () => element(by.id('before-t')), + aboveTrigger: () => element(by.id('above-t')), + combinedTrigger: () => element(by.id('combined-t')), + beforeMenu: () => element(by.css('.mat-menu-panel.before')), + aboveMenu: () => element(by.css('.mat-menu-panel.above')), + combinedMenu: () => element(by.css('.mat-menu-panel.combined')), + getResultText: () => page.textArea().getText(), + }; + + beforeEach(async () => await browser.get('/menu')); it('should open menu when the trigger is clicked', async () => { - expectToExist(menuSelector, false); - page.trigger().click(); + await expectToExist(menuSelector, false); + await page.trigger().click(); - expectToExist(menuSelector); + await expectToExist(menuSelector); expect(await page.menu().getText()).toEqual('One\nTwo\nThree\nFour'); }); - it('should close menu when menu item is clicked', () => { - page.trigger().click(); - page.items(0).click(); - expectToExist(menuSelector, false); + it('should close menu when menu item is clicked', async () => { + await page.trigger().click(); + await page.items(0).click(); + await expectToExist(menuSelector, false); }); it('should run click handlers on regular menu items', async () => { - page.trigger().click(); - page.items(0).click(); + await page.trigger().click(); + await page.items(0).click(); expect(await page.getResultText()).toEqual('one'); - page.trigger().click(); - page.items(1).click(); + await page.trigger().click(); + await page.items(1).click(); expect(await page.getResultText()).toEqual('two'); }); it('should run not run click handlers on disabled menu items', async () => { - page.trigger().click(); - page.items(2).click(); + await page.trigger().click(); + await page.items(2).click(); expect(await page.getResultText()).toEqual(''); }); it('should support multiple triggers opening the same menu', async () => { - page.triggerTwo().click(); + await page.triggerTwo().click(); expect(await page.menu().getText()).toEqual('One\nTwo\nThree\nFour'); - expectAlignedWith(page.menu(), '#trigger-two'); + await expectAlignedWith(page.menu(), '#trigger-two'); - page.backdrop().click(); + await page.backdrop().click(); await browser.wait(not(presenceOf(element(by.css(menuSelector))))); await browser.wait(not(presenceOf(element(by.css('.cdk-overlay-backdrop'))))); - page.trigger().click(); + await page.trigger().click(); expect(await page.menu().getText()).toEqual('One\nTwo\nThree\nFour'); - expectAlignedWith(page.menu(), '#trigger'); + await expectAlignedWith(page.menu(), '#trigger'); - page.backdrop().click(); + await page.backdrop().click(); await browser.wait(not(presenceOf(element(by.css(menuSelector))))); await browser.wait(not(presenceOf(element(by.css('.cdk-overlay-backdrop'))))); }); - it('should mirror classes on host to menu template in overlay', () => { - page.trigger().click(); - expect(page.menu().getAttribute('class')).toContain('mat-menu-panel'); - expect(page.menu().getAttribute('class')).toContain('custom'); + it('should mirror classes on host to menu template in overlay', async () => { + await page.trigger().click(); + expect(await page.menu().getAttribute('class')).toContain('mat-menu-panel'); + expect(await page.menu().getAttribute('class')).toContain('custom'); }); describe('keyboard events', () => { - beforeEach(() => { + beforeEach(async () => { // click start button to avoid tabbing past navigation - page.start().click(); - pressKeys(Key.TAB); + await page.start().click(); + await pressKeys(Key.TAB); }); - it('should auto-focus the first item when opened with ENTER', () => { - pressKeys(Key.ENTER); - expectFocusOn(page.items(0)); + it('should auto-focus the first item when opened with ENTER', async () => { + await pressKeys(Key.ENTER); + await expectFocusOn(page.items(0)); }); - it('should auto-focus the first item when opened with SPACE', () => { - pressKeys(Key.SPACE); - expectFocusOn(page.items(0)); + it('should auto-focus the first item when opened with SPACE', async () => { + await pressKeys(Key.SPACE); + await expectFocusOn(page.items(0)); }); - it('should focus the first item when opened by mouse', () => { - page.trigger().click(); - expectFocusOn(page.items(0)); + it('should focus the first item when opened by mouse', async () => { + await page.trigger().click(); + await expectFocusOn(page.items(0)); }); - it('should focus subsequent items when down arrow is pressed', () => { - pressKeys(Key.ENTER, Key.DOWN); - expectFocusOn(page.items(1)); + it('should focus subsequent items when down arrow is pressed', async () => { + await pressKeys(Key.ENTER, Key.DOWN); + await expectFocusOn(page.items(1)); }); - it('should focus previous items when up arrow is pressed', () => { - pressKeys(Key.ENTER, Key.DOWN, Key.UP); - expectFocusOn(page.items(0)); + it('should focus previous items when up arrow is pressed', async () => { + await pressKeys(Key.ENTER, Key.DOWN, Key.UP); + await expectFocusOn(page.items(0)); }); - it('should skip disabled items using arrow keys', () => { - pressKeys(Key.ENTER, Key.DOWN, Key.DOWN); - expectFocusOn(page.items(3)); + it('should skip disabled items using arrow keys', async () => { + await pressKeys(Key.ENTER, Key.DOWN, Key.DOWN); + await expectFocusOn(page.items(3)); - pressKeys(Key.UP); - expectFocusOn(page.items(1)); + await pressKeys(Key.UP); + await expectFocusOn(page.items(1)); }); - it('should close the menu when tabbing past items', () => { - pressKeys(Key.ENTER, Key.TAB); - expectToExist(menuSelector, false); + it('should close the menu when tabbing past items', async () => { + await pressKeys(Key.ENTER, Key.TAB); + await expectToExist(menuSelector, false); - pressKeys(Key.TAB, Key.ENTER); - expectToExist(menuSelector); + await pressKeys(Key.TAB, Key.ENTER); + await expectToExist(menuSelector); - pressKeys(protractor.Key.chord(Key.SHIFT, Key.TAB)); - expectToExist(menuSelector, false); + await pressKeys(protractor.Key.chord(Key.SHIFT, Key.TAB)); + await expectToExist(menuSelector, false); }); - it('should wrap back to menu when arrow keying past items', () => { + it('should wrap back to menu when arrow keying past items', async () => { let down = Key.DOWN; - pressKeys(Key.ENTER, down, down, down); - expectFocusOn(page.items(0)); + await pressKeys(Key.ENTER, down, down, down); + await expectFocusOn(page.items(0)); - pressKeys(Key.UP); - expectFocusOn(page.items(3)); + await pressKeys(Key.UP); + await expectFocusOn(page.items(3)); }); - it('should focus before and after trigger when tabbing past items', () => { + it('should focus before and after trigger when tabbing past items', async () => { let shiftTab = protractor.Key.chord(Key.SHIFT, Key.TAB); - pressKeys(Key.ENTER, Key.TAB); - expectFocusOn(page.triggerTwo()); + await pressKeys(Key.ENTER, Key.TAB); + await expectFocusOn(page.triggerTwo()); // navigate back to trigger - pressKeys(shiftTab, Key.ENTER, shiftTab); - expectFocusOn(page.start()); + await pressKeys(shiftTab, Key.ENTER, shiftTab); + await expectFocusOn(page.start()); }); }); @@ -150,14 +165,14 @@ describe('menu', () => { describe('position - ', () => { it('should default menu alignment to "after below" when not set', async () => { - page.trigger().click(); + await page.trigger().click(); // menu.x should equal trigger.x, menu.y should equal trigger.y await expectAlignedWith(page.menu(), '#trigger'); }); it('should align overlay end to origin end when x-position is "before"', async () => { - page.beforeTrigger().click(); + await page.beforeTrigger().click(); const trigger = await page.beforeTrigger().getLocation(); @@ -165,11 +180,11 @@ describe('menu', () => { // menu = 112px wide. trigger = 60px wide. 112 - 60 = 52px of menu to the left of trigger. // trigger.x (left corner) - 52px (menu left of trigger) = expected menu.x (left corner) // menu.y should equal trigger.y because only x position has changed. - expectLocation(page.beforeMenu(), {x: trigger.x - 52, y: trigger.y}); + await expectLocation(page.beforeMenu(), {x: trigger.x - 52, y: trigger.y}); }); it('should align overlay bottom to origin bottom when y-position is "above"', async () => { - page.aboveTrigger().click(); + await page.aboveTrigger().click(); const trigger = await page.aboveTrigger().getLocation(); @@ -177,37 +192,18 @@ describe('menu', () => { // menu.x should equal trigger.x because only y position has changed. // menu = 64px high. trigger = 20px high. 64 - 20 = 44px of menu extending up past trigger. // trigger.y (top corner) - 44px (menu above trigger) = expected menu.y (top corner) - expectLocation(page.aboveMenu(), {x: trigger.x, y: trigger.y - 44}); + await expectLocation(page.aboveMenu(), {x: trigger.x, y: trigger.y - 44}); }); it('should align menu to top left of trigger when "below" and "above"', async () => { - page.combinedTrigger().click(); + await page.combinedTrigger().click(); const trigger = await page.combinedTrigger().getLocation(); // trigger.x (left corner) - 52px (menu left of trigger) = expected menu.x // trigger.y (top corner) - 44px (menu above trigger) = expected menu.y - expectLocation(page.combinedMenu(), {x: trigger.x - 52, y: trigger.y - 44}); + await expectLocation(page.combinedMenu(), {x: trigger.x - 52, y: trigger.y - 44}); }); }); }); - - -export class MenuPage { - constructor() { browser.get('/menu'); } - menu = () => element(by.css('.mat-menu-panel')); - start = () => element(by.id('start')); - trigger = () => element(by.id('trigger')); - triggerTwo = () => element(by.id('trigger-two')); - backdrop = () => element(by.css('.cdk-overlay-backdrop')); - items = (index: number) => element.all(by.css('[mat-menu-item]')).get(index); - textArea = () => element(by.id('text')); - beforeTrigger = () => element(by.id('before-t')); - aboveTrigger = () => element(by.id('above-t')); - combinedTrigger = () => element(by.id('combined-t')); - beforeMenu = () => element(by.css('.mat-menu-panel.before')); - aboveMenu = () => element(by.css('.mat-menu-panel.above')); - combinedMenu = () => element(by.css('.mat-menu-panel.combined')); - getResultText = () => this.textArea().getText(); -} diff --git a/e2e/components/progress-bar-e2e.spec.ts b/e2e/components/progress-bar-e2e.spec.ts index 3bf454764820..280b09d44740 100644 --- a/e2e/components/progress-bar-e2e.spec.ts +++ b/e2e/components/progress-bar-e2e.spec.ts @@ -2,21 +2,21 @@ import {browser} from 'protractor'; import {expectToExist} from '../util/index'; describe('progress-bar', () => { - beforeEach(() => browser.get('/progress-bar')); + beforeEach(async () => await browser.get('/progress-bar')); - it('should render a determinate progress bar', () => { - expectToExist('mat-progress-bar[mode="determinate"]'); + it('should render a determinate progress bar', async () => { + await expectToExist('mat-progress-bar[mode="determinate"]'); }); - it('should render a buffer progress bar', () => { - expectToExist('mat-progress-bar[mode="buffer"]'); + it('should render a buffer progress bar', async () => { + await expectToExist('mat-progress-bar[mode="buffer"]'); }); - it('should render a query progress bar', () => { - expectToExist('mat-progress-bar[mode="query"]'); + it('should render a query progress bar', async () => { + await expectToExist('mat-progress-bar[mode="query"]'); }); - it('should render a indeterminate progress bar', () => { - expectToExist('mat-progress-bar[mode="indeterminate"]'); + it('should render a indeterminate progress bar', async () => { + await expectToExist('mat-progress-bar[mode="indeterminate"]'); }); }); diff --git a/e2e/components/progress-spinner-e2e.spec.ts b/e2e/components/progress-spinner-e2e.spec.ts index 07a23f765288..28f8eed9b6d9 100644 --- a/e2e/components/progress-spinner-e2e.spec.ts +++ b/e2e/components/progress-spinner-e2e.spec.ts @@ -1,7 +1,7 @@ import {browser, by, element} from 'protractor'; describe('progress-spinner', () => { - beforeEach(() => browser.get('/progress-spinner')); + beforeEach(async () => await browser.get('/progress-spinner')); it('should render a determinate progress spinner', async () => { expect(await element(by.css('mat-progress-spinner')).isPresent()).toBe(true); diff --git a/e2e/components/radio-e2e.spec.ts b/e2e/components/radio-e2e.spec.ts index f39e65eb9d67..67f96e0a8a5a 100644 --- a/e2e/components/radio-e2e.spec.ts +++ b/e2e/components/radio-e2e.spec.ts @@ -3,39 +3,37 @@ import {browser, by, element, ExpectedConditions} from 'protractor'; describe('radio', () => { describe('disabling behavior', () => { - beforeEach(() => browser.get('/radio')); + beforeEach(async () => await browser.get('/radio')); it('should be checked when clicked', async () => { - element(by.id('water')).click(); + await element(by.id('water')).click(); - expect(element(by.id('water')).getAttribute('class')).toContain('mat-radio-checked'); + expect(await element(by.id('water')).getAttribute('class')).toContain('mat-radio-checked'); - expect(element(by.css('input[id=water-input]')).getAttribute('checked')).toBeTruthy(); - expect(element(by.css('input[id=leaf-input]')).getAttribute('checked')).toBeFalsy(); + expect(await element(by.css('input[id=water-input]')).getAttribute('checked')).toBeTruthy(); + expect(await element(by.css('input[id=leaf-input]')).getAttribute('checked')).toBeFalsy(); - element(by.id('leaf')).click(); - expect(element(by.id('leaf')).getAttribute('class')).toContain('mat-radio-checked'); + await element(by.id('leaf')).click(); + expect(await element(by.id('leaf')).getAttribute('class')).toContain('mat-radio-checked'); - expect(element(by.css('input[id=leaf-input]')).getAttribute('checked')).toBeTruthy(); - expect(element(by.css('input[id=water-input]')).getAttribute('checked')).toBeFalsy(); + expect(await element(by.css('input[id=leaf-input]')).getAttribute('checked')).toBeTruthy(); + expect(await element(by.css('input[id=water-input]')).getAttribute('checked')).toBeFalsy(); }); it('should be disabled when disable the radio group', async () => { - element(by.id('toggle-disable')).click(); - element(by.id('water')).click(); + await element(by.id('toggle-disable')).click(); + await element(by.id('water')).click(); - expect(element(by.id('water')).getAttribute('class')).toContain('mat-radio-disabled'); + expect(await element(by.id('water')).getAttribute('class')).toContain('mat-radio-disabled'); await browser.wait(ExpectedConditions.presenceOf(element(by.css('.mat-radio-disabled')))); - expect(element(by.css('input[id=water-input]')).getAttribute('disabled')).toBeTruthy(); + expect(await element(by.css('input[id=water-input]')).getAttribute('disabled')).toBeTruthy(); - element(by.id('leaf')).click(); - expect(element(by.id('leaf')).getAttribute('class')).toContain('mat-radio-disabled'); + await element(await by.id('leaf')).click(); + expect(await element(by.id('leaf')).getAttribute('class')).toContain('mat-radio-disabled'); - expect(element(by.css('input[id=leaf-input]')).getAttribute('disabled')).toBeTruthy(); + expect(await element(by.css('input[id=leaf-input]')).getAttribute('disabled')).toBeTruthy(); }); - }); - }); diff --git a/e2e/components/sidenav-e2e.spec.ts b/e2e/components/sidenav-e2e.spec.ts index 9f90e4403652..f2ba10d49491 100644 --- a/e2e/components/sidenav-e2e.spec.ts +++ b/e2e/components/sidenav-e2e.spec.ts @@ -4,26 +4,26 @@ describe('sidenav', () => { describe('opening and closing', () => { let sidenav: ElementFinder; - beforeEach(() => { - browser.get('/sidenav'); + beforeEach(async () => { + await browser.get('/sidenav'); sidenav = element(by.tagName('mat-sidenav')); }); - it('should be closed', () => { - expect(sidenav.isDisplayed()).toBeFalsy(); + it('should be closed', async () => { + expect(await sidenav.isDisplayed()).toBeFalsy(); }); - it('should open', () => { - element(by.buttonText('Open sidenav')).click(); - expect(sidenav.isDisplayed()).toBeTruthy(); + it('should open', async () => { + await element(by.buttonText('Open sidenav')).click(); + expect(await sidenav.isDisplayed()).toBeTruthy(); }); - it('should close again', () => { - element(by.buttonText('Open sidenav')).click(); - browser.sleep(50); - element(by.buttonText('Open sidenav')).click(); + it('should close again', async () => { + await element(by.buttonText('Open sidenav')).click(); + await browser.sleep(50); + await element(by.buttonText('Open sidenav')).click(); - expect(sidenav.isDisplayed()).toBeFalsy(); + expect(await sidenav.isDisplayed()).toBeFalsy(); }); }); }); diff --git a/e2e/components/slide-toggle-e2e.spec.ts b/e2e/components/slide-toggle-e2e.spec.ts index 34774c9e2a4b..531565fe4c8c 100644 --- a/e2e/components/slide-toggle-e2e.spec.ts +++ b/e2e/components/slide-toggle-e2e.spec.ts @@ -6,40 +6,46 @@ describe('slide-toggle', () => { const getInput = () => element(by.css('#normal-slide-toggle input')); const getNormalToggle = () => element(by.css('#normal-slide-toggle')); - beforeEach(() => browser.get('slide-toggle')); + beforeEach(async () => await browser.get('slide-toggle')); - it('should render a slide-toggle', () => { - expectToExist('mat-slide-toggle'); + it('should render a slide-toggle', async () => { + await expectToExist('mat-slide-toggle'); }); it('should change the checked state on click', async () => { const inputEl = getInput(); - expect(inputEl.getAttribute('checked')).toBeFalsy('Expect slide-toggle to be unchecked'); + expect(await inputEl.getAttribute('checked')) + .toBeFalsy('Expect slide-toggle to be unchecked'); - getNormalToggle().click(); + await getNormalToggle().click(); - expect(inputEl.getAttribute('checked')).toBeTruthy('Expect slide-toggle to be checked'); + expect(await inputEl.getAttribute('checked')) + .toBeTruthy('Expect slide-toggle to be checked'); }); it('should change the checked state on click', async () => { const inputEl = getInput(); - expect(inputEl.getAttribute('checked')).toBeFalsy('Expect slide-toggle to be unchecked'); + expect(await inputEl.getAttribute('checked')) + .toBeFalsy('Expect slide-toggle to be unchecked'); - getNormalToggle().click(); + await getNormalToggle().click(); - expect(inputEl.getAttribute('checked')).toBeTruthy('Expect slide-toggle to be checked'); + expect(await inputEl.getAttribute('checked')) + .toBeTruthy('Expect slide-toggle to be checked'); }); it('should not change the checked state on click when disabled', async () => { const inputEl = getInput(); - expect(inputEl.getAttribute('checked')).toBeFalsy('Expect slide-toggle to be unchecked'); + expect(await inputEl.getAttribute('checked')) + .toBeFalsy('Expect slide-toggle to be unchecked'); - element(by.css('#disabled-slide-toggle')).click(); + await element(by.css('#disabled-slide-toggle')).click(); - expect(inputEl.getAttribute('checked')).toBeFalsy('Expect slide-toggle to be unchecked'); + expect(await inputEl.getAttribute('checked')) + .toBeFalsy('Expect slide-toggle to be unchecked'); }); it('should move the thumb on state change', async () => { @@ -47,21 +53,23 @@ describe('slide-toggle', () => { const thumbEl = element(by.css('#normal-slide-toggle .mat-slide-toggle-thumb-container')); const previousPosition = await thumbEl.getLocation(); - slideToggleEl.click(); + await slideToggleEl.click(); const position = await thumbEl.getLocation(); expect(position.x).not.toBe(previousPosition.x); }); - it('should toggle the slide-toggle on space key', () => { + it('should toggle the slide-toggle on space key', async () => { const inputEl = getInput(); - expect(inputEl.getAttribute('checked')).toBeFalsy('Expect slide-toggle to be unchecked'); + expect(await inputEl.getAttribute('checked')) + .toBeFalsy('Expect slide-toggle to be unchecked'); - inputEl.sendKeys(Key.SPACE); + await inputEl.sendKeys(Key.SPACE); - expect(inputEl.getAttribute('checked')).toBeTruthy('Expect slide-toggle to be checked'); + expect(await inputEl.getAttribute('checked')) + .toBeTruthy('Expect slide-toggle to be checked'); }); }); diff --git a/e2e/components/stepper-e2e.spec.ts b/e2e/components/stepper-e2e.spec.ts index 219819d4cc63..af3277c6e502 100644 --- a/e2e/components/stepper-e2e.spec.ts +++ b/e2e/components/stepper-e2e.spec.ts @@ -6,10 +6,10 @@ import {pressKeys} from '../util/actions'; import {Key} from 'selenium-webdriver'; describe('stepper', () => { - beforeEach(() => browser.get('/stepper')); + beforeEach(async () => await browser.get('/stepper')); - it('should render a stepper', () => { - expectToExist('mat-horizontal-stepper'); + it('should render a stepper', async () => { + await expectToExist('mat-horizontal-stepper'); }); describe('basic behavior', () => { @@ -20,7 +20,7 @@ describe('stepper', () => { expect(await element(by.css('mat-step-header[aria-selected="true"]')).getText()) .toBe('1\nFill out your name'); - nextButton.get(0).click(); + await nextButton.get(0).click(); expect(await element(by.css('mat-step-header[aria-selected="true"]')).getText()) .toBe('2\nFill out your address'); @@ -28,7 +28,7 @@ describe('stepper', () => { await browser.wait(ExpectedConditions.not( ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))); - previousButton.get(0).click(); + await previousButton.get(0).click(); expect(await element(by.css('mat-step-header[aria-selected="true"]')).getText()) .toBe('1\nFill out your name'); @@ -37,40 +37,40 @@ describe('stepper', () => { ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))); }); - it('should change focus with keyboard interaction', () => { - let stepHeaders = element.all(by.css('mat-step-header')); - stepHeaders.get(0).click(); + it('should change focus with keyboard interaction', async () => { + const stepHeaders = element.all(by.css('mat-step-header')); + await stepHeaders.get(0).click(); - expectFocusOn(stepHeaders.get(0)); + await expectFocusOn(stepHeaders.get(0)); - pressKeys(Key.RIGHT); - expectFocusOn(stepHeaders.get(1)); + await pressKeys(Key.RIGHT); + await expectFocusOn(stepHeaders.get(1)); - pressKeys(Key.RIGHT); - expectFocusOn(stepHeaders.get(2)); + await pressKeys(Key.RIGHT); + await expectFocusOn(stepHeaders.get(2)); - pressKeys(Key.RIGHT); - expectFocusOn(stepHeaders.get(0)); + await pressKeys(Key.RIGHT); + await expectFocusOn(stepHeaders.get(0)); - pressKeys(Key.LEFT); - expectFocusOn(stepHeaders.get(2)); + await pressKeys(Key.LEFT); + await expectFocusOn(stepHeaders.get(2)); - pressKeys(Key.SPACE, Key.ENTER); - expectFocusOn(stepHeaders.get(2)); + await pressKeys(Key.SPACE, Key.ENTER); + await expectFocusOn(stepHeaders.get(2)); }); }); describe('linear stepper', () => { let linearButton: ElementFinder; - beforeEach(() => { + beforeEach(async () => { linearButton = element(by.id('toggle-linear')); - linearButton.click(); + await linearButton.click(); }); it('should not move to next step when stepper button is clicked', async () => { - let nextButton = element.all(by.buttonText('Next')); - nextButton.get(0).click(); + const nextButton = element.all(by.buttonText('Next')); + await nextButton.get(0).click(); expect(await element(by.css('mat-step-header[aria-selected="true"]')).getText()) .toBe('1\nFill out your name'); diff --git a/e2e/components/tabs-e2e.spec.ts b/e2e/components/tabs-e2e.spec.ts index f37e96ee03e0..459bc049e999 100644 --- a/e2e/components/tabs-e2e.spec.ts +++ b/e2e/components/tabs-e2e.spec.ts @@ -13,21 +13,21 @@ describe('tabs', () => { let tabLabels: ElementArrayFinder; let tabBodies: ElementArrayFinder; - beforeEach(() => { - browser.get('/tabs'); + beforeEach(async () => { + await browser.get('/tabs'); tabLabels = element.all(by.css('.mat-tab-label')); tabBodies = element.all(by.css('mat-tab-body')); }); it('should change tabs when the label is clicked', async () => { - tabLabels.get(1).click(); + await tabLabels.get(1).click(); expect(await getLabelActiveStates(tabLabels)).toEqual([false, true, false]); expect(await getBodyActiveStates(tabBodies)).toEqual([false, true, false]); await browser.wait(ExpectedConditions.not( ExpectedConditions.presenceOf(element(by.css('div.mat-ripple-element'))))); - tabLabels.get(0).click(); + await tabLabels.get(0).click(); expect(await getLabelActiveStates(tabLabels)).toEqual([true, false, false]); expect(await getBodyActiveStates(tabBodies)).toEqual([true, false, false]); @@ -39,19 +39,19 @@ describe('tabs', () => { const right = Key.RIGHT; const left = Key.LEFT; - tabLabels.get(0).click(); + await tabLabels.get(0).click(); expect(await getFocusStates(tabLabels)).toEqual([true, false, false]); - pressKeys(right); + await pressKeys(right); expect(await getFocusStates(tabLabels)).toEqual([false, true, false]); - pressKeys(right); + await pressKeys(right); expect(await getFocusStates(tabLabels)).toEqual([false, false, true]); - pressKeys(left); + await pressKeys(left); expect(await getFocusStates(tabLabels)).toEqual([false, true, false]); - pressKeys(left); + await pressKeys(left); expect(await getFocusStates(tabLabels)).toEqual([true, false, false]); }); }); diff --git a/e2e/components/toolbar-e2e.spec.ts b/e2e/components/toolbar-e2e.spec.ts index 601ae98a1cca..2edd111be49b 100644 --- a/e2e/components/toolbar-e2e.spec.ts +++ b/e2e/components/toolbar-e2e.spec.ts @@ -2,10 +2,10 @@ import {browser, by, element} from 'protractor'; describe('mat-toolbar', () => { - beforeEach(() => browser.get('/toolbar')); + beforeEach(async () => await browser.get('/toolbar')); it('should show a toolbar', async () => { - expect(element(by.tagName('mat-toolbar'))).toBeDefined(); + expect(await element(by.tagName('mat-toolbar'))).toBeDefined(); }); }); diff --git a/e2e/components/virtual-scroll-e2e.spec.ts b/e2e/components/virtual-scroll-e2e.spec.ts index 57084e95000e..60d61540b64a 100644 --- a/e2e/components/virtual-scroll-e2e.spec.ts +++ b/e2e/components/virtual-scroll-e2e.spec.ts @@ -8,8 +8,8 @@ describe('autosize cdk-virtual-scroll', () => { let viewport: ElementFinder; describe('with uniform items', () => { - beforeEach(() => { - browser.get('/virtual-scroll'); + beforeEach(async () => { + await browser.get('/virtual-scroll'); viewport = element(by.css('.demo-virtual-scroll-uniform-size cdk-virtual-scroll-viewport')); }); @@ -38,8 +38,8 @@ describe('autosize cdk-virtual-scroll', () => { }); describe('with variable size', () => { - beforeEach(() => { - browser.get('/virtual-scroll'); + beforeEach(async () => { + await browser.get('/virtual-scroll'); viewport = element(by.css('.demo-virtual-scroll-variable-size cdk-virtual-scroll-viewport')); }); diff --git a/e2e/index-e2e.spec.ts b/e2e/index-e2e.spec.ts index 9464a17605c3..cae783b1569e 100644 --- a/e2e/index-e2e.spec.ts +++ b/e2e/index-e2e.spec.ts @@ -2,7 +2,9 @@ import {browser} from 'protractor'; describe('hello, protractor', () => { describe('index', () => { - browser.get('/'); + + beforeAll(async () => browser.get('/')); + it('should have a title', async () => { expect(await browser.getTitle()).toBe('Angular Material'); }); diff --git a/e2e/protractor.conf.js b/e2e/protractor.conf.js new file mode 100644 index 000000000000..5bdf73471262 --- /dev/null +++ b/e2e/protractor.conf.js @@ -0,0 +1,28 @@ +exports.config = { + useAllAngular2AppRoots: true, + allScriptsTimeout: 120000, + getPageTimeout: 120000, + jasmineNodeOpts: { + defaultTimeoutInterval: 120000, + }, + + plugins: [ + { + // Runs the axe-core accessibility checks each time the e2e page changes and + // Angular is ready. + path: require.resolve('angular_material/tools/axe-protractor'), + + rules: [ + // Exclude mat-menu elements because those are empty if not active. + {id: 'aria-required-children', selector: '*:not(mat-menu)'}, + + // Disable color contrast checks since the final colors will vary based on the theme. + {id: 'color-contrast', enabled: false}, + ] + } + ], + + // Since we want to use async/await we don't want to mix up with selenium's promise + // manager. In order to enforce this, we disable the promise manager. + SELENIUM_PROMISE_MANAGER: false, +}; diff --git a/e2e/start-devserver.js b/e2e/start-devserver.js new file mode 100644 index 000000000000..cee1c15f9cac --- /dev/null +++ b/e2e/start-devserver.js @@ -0,0 +1,42 @@ +const protractor = require('protractor'); +const utils = require('@angular/bazel/protractor-utils'); +const spawn = require('child_process').spawn; + +/** + * Runs the specified server binary from a given workspace and waits for the server + * being ready. The server binary will be resolved from the runfiles. + */ +async function runBazelServer(workspace, serverPath, timeout) { + const serverBinary = require.resolve(`${workspace}/${serverPath}`); + const port = await utils.findFreeTcpPort(); + + // Start the Bazel server binary with a random free TCP port. + const serverProcess = spawn(serverBinary, ['-port', port], {stdio: 'inherit'}); + + // In case the process exited with an error, we want to propagate the error. + serverProcess.on('exit', exitCode => { + if (exitCode !== 0) { + throw new Error(`Server exited with error code: ${exitCode}`); + } + }); + + // Wait for the server to be bound to the given port. + await utils.waitForServer(port, timeout); + + return port; +} + +/** + * Called by Protractor before starting any tests. This is script is responsible for + * starting up the devserver and updating the Protractor base URL to the proper port. + */ +module.exports = async function(config) { + const port = await runBazelServer(config.workspace, config.server); + const baseUrl = `http://localhost:${port}`; + const processedConfig = await protractor.browser.getProcessedConfig(); + + // Update the protractor "baseUrl" to match the new random TCP port. We need random TCP ports + // because otherwise Bazel could not execute protractor tests concurrently. + protractor.browser.baseUrl = baseUrl; + processedConfig.baseUrl = baseUrl; +}; diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json index 00a2fb783dff..170ed637d77a 100644 --- a/e2e/tsconfig.json +++ b/e2e/tsconfig.json @@ -1,26 +1,6 @@ -// TypeScript config that is used to build the e2e spec files. Protractor will use TS-Node to -// compile the e2e/ folder at runtime. { "compilerOptions": { - "declaration": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "noImplicitThis": true, - "inlineSources": true, "lib": ["es2015"], - "module": "commonjs", - "moduleResolution": "node", - "noEmitOnError": true, - "noImplicitAny": true, - "outDir": "../dist/e2e/", - "rootDir": ".", - "sourceMap": true, - "target": "es5", - "typeRoots": [ - "../node_modules/@types" - ], "types": [ "jasmine" ] diff --git a/e2e/util/actions.ts b/e2e/util/actions.ts index 8199481a8951..572c1c7b569d 100644 --- a/e2e/util/actions.ts +++ b/e2e/util/actions.ts @@ -4,18 +4,18 @@ import {getElement, FinderResult} from './query'; /** * Presses a single key or a sequence of keys. */ -export function pressKeys(...keys: string[]): void { - let actions = browser.actions(); - actions.sendKeys.call(actions, keys).perform(); +export async function pressKeys(...keys: string[]) { + const actions = browser.actions(); + await actions.sendKeys.call(actions, keys).perform(); } /** * Clicks an element at a specific point. Useful if there's another element * that covers part of the target and can catch the click. */ -export function clickElementAtPoint(element: FinderResult, coords: Point): void { - let webElement = getElement(element).getWebElement(); - browser.actions().mouseMove(webElement, coords).click().perform(); +export async function clickElementAtPoint(element: FinderResult, coords: Point) { + const webElement = await getElement(element).getWebElement(); + await browser.actions().mouseMove(webElement, coords).click().perform(); } export interface Point { x: number; y: number; } diff --git a/e2e/util/asserts.ts b/e2e/util/asserts.ts index 70467ffe2c9d..3d20ae95d6e4 100644 --- a/e2e/util/asserts.ts +++ b/e2e/util/asserts.ts @@ -5,8 +5,8 @@ import {Point} from './actions'; /** * Asserts that an element exists. */ -export function expectToExist(selector: string, expected = true) { - return waitForElement(selector).then((isPresent: boolean) => { +export async function expectToExist(selector: string, expected = true) { + await waitForElement(selector).then((isPresent: boolean) => { expect(isPresent).toBe(expected, `Expected "${selector}"${expected ? '' : ' not'} to exist`); }); } @@ -14,16 +14,16 @@ export function expectToExist(selector: string, expected = true) { /** * Asserts that an element is focused. */ -export function expectFocusOn(element: FinderResult, expected = true): void { - expect(browser.driver.switchTo().activeElement().getId()).toBe( - getElement(element).getId(), `Expected element${expected ? '' : ' not'} to be focused.`); +export async function expectFocusOn(element: FinderResult, expected = true) { + expect(await browser.driver.switchTo().activeElement().getId()).toBe( + await getElement(element).getId(), `Expected element${expected ? '' : ' not'} to be focused.`); } /** * Asserts that an element has a certain location. */ -export function expectLocation(element: FinderResult, {x, y}: Point): void { - getElement(element).getLocation().then((location: Point) => { +export async function expectLocation(element: FinderResult, {x, y}: Point) { + await getElement(element).getLocation().then((location: Point) => { expect(Math.round(location.x)).toEqual(Math.round(x)); expect(Math.round(location.y)).toEqual(Math.round(y)); }); @@ -32,8 +32,8 @@ export function expectLocation(element: FinderResult, {x, y}: Point): void { /** * Asserts that one element is aligned with another. */ -export function expectAlignedWith(element: FinderResult, otherElement: FinderResult): void { - getElement(otherElement).getLocation().then((location: Point) => { +export async function expectAlignedWith(element: FinderResult, otherElement: FinderResult) { + await getElement(otherElement).getLocation().then((location: Point) => { expectLocation(getElement(element), location); }); } diff --git a/e2e/util/query.ts b/e2e/util/query.ts index 61186685f721..364766922e08 100644 --- a/e2e/util/query.ts +++ b/e2e/util/query.ts @@ -1,4 +1,4 @@ -import {ElementFinder, by, element, ProtractorBy, browser} from 'protractor'; +import {browser, by, element, ElementFinder} from 'protractor'; import {Point} from './actions'; /** @@ -12,8 +12,8 @@ export function getElement(el: FinderResult): ElementFinder { /** * Waits for an element to be rendered. */ -export function waitForElement(selector: string) { - return browser.isElementPresent(by.css(selector) as ProtractorBy); +export async function waitForElement(selector: string) { + return await browser.isElementPresent(by.css(selector)); } /** diff --git a/src/e2e-app/BUILD.bazel b/src/e2e-app/BUILD.bazel new file mode 100644 index 000000000000..758606a8f7c9 --- /dev/null +++ b/src/e2e-app/BUILD.bazel @@ -0,0 +1,57 @@ +package(default_visibility=["//visibility:public"]) + +load("@build_bazel_rules_typescript//:defs.bzl", "ts_devserver") +load("//tools:defaults.bzl", "ng_module") + +ng_module( + name = "e2e-app", + srcs = glob(["**/*.ts"]), + assets = glob(["**/*.html", "**/*.css"], exclude = ["index.html"]), + deps = [ + "@angular//packages/core", + "@angular//packages/forms", + "@angular//packages/platform-browser", + "@angular//packages/platform-browser/animations", + "@angular//packages/router", + "//src/cdk/drag-drop", + "//src/cdk/overlay", + "//src/cdk/scrolling", + # TODO(devversion): be more explicit by using "@angular/material" with individual entry points. + "//src/lib:material", + "//src/material-examples:examples", + "//src/cdk-experimental/dialog", + "//src/cdk-experimental/scrolling", + ], +) + +ts_devserver( + name = "devserver", + port = 4200, + # Name of the AMD module that should be required on page load. + entry_module = "angular_material/src/e2e-app/main", + # Serving path of the bundle that serves all files specified in "deps" and "scripts". + serving_path = "/bundle.js", + # Root paths can be used simplify the loading of files from external Bazel repositories + # (such as the Bazel managed deps repository called "matdeps") + additional_root_paths = [ + "matdeps/node_modules", + ], + # Files which should be provided by Bazel when running the devserver. These are not + # automatically served, but can be loaded manually through HTTP requests. + static_files = [ + "@matdeps//zone.js", + "@matdeps//core-js", + "@matdeps//hammerjs", + "//src/lib/prebuilt-themes:indigo-pink", + ":index.html", + ], + # Scripts which will be included in the serving_path bundle after "require.js" has been + # loaded. + scripts = [ + ":devserver-configure.js", + "@matdeps//node_modules/tslib:tslib.js", + ], + # Dependencies that produce JavaScript output will be automatically included in the + # serving_path bundle + deps = [":e2e-app"], +) diff --git a/src/e2e-app/devserver-configure.js b/src/e2e-app/devserver-configure.js new file mode 100644 index 000000000000..4d0a13973c62 --- /dev/null +++ b/src/e2e-app/devserver-configure.js @@ -0,0 +1,10 @@ +// We need to configure AMD modules which are not named because otherwise "require.js" is not +// able to resolve AMD imports to such modules. +require.config({ + paths: { + 'moment': 'moment/min/moment.min' + } +}); + +// Workaround until https://github.com/angular/material2/issues/13883 has been addressed. +var module = {id: ''}; diff --git a/src/e2e-app/index.html b/src/e2e-app/index.html index 503071049caf..6b8d0b685c06 100644 --- a/src/e2e-app/index.html +++ b/src/e2e-app/index.html @@ -9,7 +9,7 @@ - + @@ -17,15 +17,12 @@