From 99f8e108094e8dd2b023b9e02f8b672ea8308ff6 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Mon, 16 Apr 2018 00:02:28 +0300 Subject: [PATCH] ci(aio): fix `aio-monitoring` tests (#23390) Previously, we were running the e2e tests from master against `https://angular.io` (deployed from the stable branch). Often the e2e tests from master do not apply to the stable branch, since the app has deviated slightly. This commit fixes this by stop running the full e2e tests against the deployed versions, but a smaller set of "smoke tests", which check basic functionality that is less likely to change between versions. PR Close #23390 --- aio/scripts/test-production.sh | 10 +- .../deployment/e2e/redirection.e2e-spec.ts | 31 ++---- aio/tests/deployment/e2e/site.po.ts | 60 ++++++++++ .../deployment/e2e/smoke-tests.e2e-spec.ts | 103 ++++++++++++++++++ 4 files changed, 175 insertions(+), 29 deletions(-) create mode 100644 aio/tests/deployment/e2e/site.po.ts create mode 100644 aio/tests/deployment/e2e/smoke-tests.e2e-spec.ts diff --git a/aio/scripts/test-production.sh b/aio/scripts/test-production.sh index e158d5e78d..313d9b946a 100755 --- a/aio/scripts/test-production.sh +++ b/aio/scripts/test-production.sh @@ -5,8 +5,7 @@ set +x -eu -o pipefail readonly thisDir="$(cd $(dirname ${BASH_SOURCE[0]}); pwd)" readonly aioDir="$(realpath $thisDir/..)" - readonly appPtorConf="$aioDir/tests/e2e/protractor.conf.js" - readonly cfgPtorConf="$aioDir/tests/deployment/e2e/protractor.conf.js" + readonly protractorConf="$aioDir/tests/deployment/e2e/protractor.conf.js" readonly minPwaScore="95" readonly urls=( "https://angular.io/" @@ -24,11 +23,8 @@ set +x -eu -o pipefail for url in "${urls[@]}"; do echo -e "\nChecking '$url'...\n-----" - # Run e2e tests. - yarn protractor "$appPtorConf" --baseUrl "$url" - - # Run deployment config tests. - yarn protractor "$cfgPtorConf" --baseUrl "$url" + # Run basic e2e and deployment config tests. + yarn protractor "$protractorConf" --baseUrl "$url" # Run PWA-score tests. yarn test-pwa-score "$url" "$minPwaScore" diff --git a/aio/tests/deployment/e2e/redirection.e2e-spec.ts b/aio/tests/deployment/e2e/redirection.e2e-spec.ts index 9c7b8d41d0..cd31d828bb 100644 --- a/aio/tests/deployment/e2e/redirection.e2e-spec.ts +++ b/aio/tests/deployment/e2e/redirection.e2e-spec.ts @@ -1,31 +1,18 @@ import { browser } from 'protractor'; +import { SitePage } from './site.po'; describe(browser.baseUrl, () => { - const sitemapUrls = browser.params.sitemapUrls; - const legacyUrls = browser.params.legacyUrls; - const goTo = async url => { - // Go to the specified URL and then unregister the ServiceWorker - // to ensure subsequent requests are passed through to the server. - await browser.get(url); - await browser.executeAsyncScript(cb => navigator.serviceWorker - .getRegistrations() - .then(regs => Promise.all(regs.map(reg => reg.unregister()))) - .then(cb)); - }; + const page = new SitePage(); - beforeAll(async done => { - // Make an initial request to unregister the ServiceWorker. - await goTo(browser.baseUrl); - done(); - }); + beforeAll(done => page.init().then(done)); beforeEach(() => browser.waitForAngularEnabled(false)); afterEach(() => browser.waitForAngularEnabled(true)); describe('(with sitemap URLs)', () => { - sitemapUrls.forEach((url, i) => { - it(`should not redirect '${url}' (${i + 1}/${sitemapUrls.length})`, async () => { - await goTo(url); + page.sitemapUrls.forEach((url, i) => { + it(`should not redirect '${url}' (${i + 1}/${page.sitemapUrls.length})`, async () => { + await page.goTo(url); const expectedUrl = browser.baseUrl + url; const actualUrl = (await browser.getCurrentUrl()).replace(/\?.*$/, ''); @@ -36,9 +23,9 @@ describe(browser.baseUrl, () => { }); describe('(with legacy URLs)', () => { - legacyUrls.forEach(([fromUrl, toUrl], i) => { - it(`should redirect '${fromUrl}' to '${toUrl}' (${i + 1}/${legacyUrls.length})`, async () => { - await goTo(fromUrl); + page.legacyUrls.forEach(([fromUrl, toUrl], i) => { + it(`should redirect '${fromUrl}' to '${toUrl}' (${i + 1}/${page.legacyUrls.length})`, async () => { + await page.goTo(fromUrl); const expectedUrl = (/^http/.test(toUrl) ? '' : browser.baseUrl.replace(/\/$/, '')) + toUrl; const actualUrl = (await browser.getCurrentUrl()).replace(/\?.*$/, ''); diff --git a/aio/tests/deployment/e2e/site.po.ts b/aio/tests/deployment/e2e/site.po.ts new file mode 100644 index 0000000000..32d1519dc2 --- /dev/null +++ b/aio/tests/deployment/e2e/site.po.ts @@ -0,0 +1,60 @@ +import { browser, by, element, ExpectedConditions } from 'protractor'; + +export class SitePage { + /** All URLs found in the app's `sitemap.xml` (i.e. valid URLs tha should not be redirected). */ + sitemapUrls: string[] = browser.params.sitemapUrls; + + /** A list of legacy URLs that should be redirected to new URLs (in the form `[fromUrl, toUrl]`). */ + legacyUrls: string[][] = browser.params.legacyUrls; + + /** + * Enter a query into the search field. + */ + async enterSearch(query: string) { + const searchInput = element(by.css('input[type=search]')); + await searchInput.clear(); + await searchInput.sendKeys(query); + } + + /** + * Get the text content of the `aio-doc-viewer` element (in lowercase). + */ + async getDocViewerText() { + const docViewer = element(by.css('aio-doc-viewer')); + const text = await docViewer.getText(); + return text.toLowerCase(); + } + + /** + * Get a list of text contents for all search result items found on the page. + */ + async getSearchResults() { + const results = element.all(by.css('.search-results li')); + await browser.wait(ExpectedConditions.presenceOf(results.first()), 8000); + return await results.map(link => link!.getText()); + } + + /** + * Navigate to a URL, disable animations, unregister the ServiceWorker, and wait for Angular. + * (The SW is unregistered to ensure that subsequent requests are passed through to the server.) + */ + async goTo(url: string) { + const unregisterServiceWorker = (cb: () => void) => navigator.serviceWorker + .getRegistrations() + .then(regs => Promise.all(regs.map(reg => reg.unregister()))) + .then(cb); + + await browser.get(url || browser.baseUrl); + await browser.executeScript('document.body.classList.add(\'no-animations\')'); + await browser.executeAsyncScript(unregisterServiceWorker); + await browser.waitForAngular(); + }; + + /** + * Initialize the page object and get it ready for further requests. + */ + async init() { + // Make an initial request to unregister the ServiceWorker. + await this.goTo(''); + } +} diff --git a/aio/tests/deployment/e2e/smoke-tests.e2e-spec.ts b/aio/tests/deployment/e2e/smoke-tests.e2e-spec.ts new file mode 100644 index 0000000000..b55aae136e --- /dev/null +++ b/aio/tests/deployment/e2e/smoke-tests.e2e-spec.ts @@ -0,0 +1,103 @@ +import { browser } from 'protractor'; +import { SitePage } from './site.po'; + +describe(browser.baseUrl, () => { + const page = new SitePage(); + + beforeAll(done => page.init().then(done)); + + beforeEach(() => browser.waitForAngularEnabled(false)); + afterEach(() => browser.waitForAngularEnabled(true)); + + describe('(smoke tests)', () => { + it('should show the home page', () => { + page.goTo(''); + const text = page.getDocViewerText(); + + expect(text).toContain('one framework'); + expect(text).toContain('mobile & desktop'); + }); + + describe('(marketing pages)', () => { + const textPerUrl = { + features: 'features & benefits', + docs: 'what is angular?', + events: 'events', + resources: 'explore angular resources', + }; + + Object.keys(textPerUrl).forEach(url => { + it(`should show the page at '${url}'`, () => { + page.goTo(url); + expect(page.getDocViewerText()).toContain(textPerUrl[url]); + }); + }); + }); + + describe('(docs pages)', () => { + const textPerUrl = { + api: 'api list', + 'guide/architecture': 'architecture', + 'guide/http': 'httpclient', + 'guide/quickstart': 'quickstart', + 'guide/security': 'security', + tutorial: 'tutorial', + }; + + Object.keys(textPerUrl).forEach(url => { + it(`should show the page at '${url}'`, () => { + page.goTo(url); + expect(page.getDocViewerText()).toContain(textPerUrl[url]); + }); + }); + }); + + describe('(api docs pages)', () => { + const textPerUrl = { + /* Class */ 'api/core/Injector': 'class injector', + /* Const */ 'api/forms/NG_VALIDATORS': 'const ng_validators', + /* Decorator */ 'api/core/Component': '@component', + /* Directive */ 'api/common/NgIf': 'class ngif', + /* Enum */ 'api/core/ChangeDetectionStrategy': 'enum changedetectionstrategy', + /* Function */ 'api/animations/animate': 'animate(', + /* Interface */ 'api/core/OnDestroy': 'interface ondestroy', + /* Pipe */ 'api/common/JsonPipe': '| json', + /* Type-Alias */ 'api/common/http/HttpEvent': 'type httpevent', + }; + + Object.keys(textPerUrl).forEach(url => { + it(`should show the page at '${url}'`, () => { + page.goTo(url); + expect(page.getDocViewerText()).toContain(textPerUrl[url]); + }); + }); + }); + + describe('(search results)', () => { + beforeEach(() => page.goTo('')); + + it('should find pages when searching by a partial word in the title', () => { + page.enterSearch('ngCont'); + expect(page.getSearchResults()).toContain('NgControl'); + }); + + it('should find API docs when searching for an instance member name', () => { + page.enterSearch('writeValue'); + expect(page.getSearchResults()).toContain('ControlValueAccessor'); + }); + + it('should find API docs when searching for a static member name', () => { + page.enterSearch('compose'); + expect(page.getSearchResults()).toContain('Validators'); + }); + }); + + it('should show relevant results on 404', () => { + page.goTo('http/router'); + const results = page.getSearchResults(); + + expect(results).toContain('HttpClient'); + expect(results).toContain('Router'); + }); + }); +});