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
This commit is contained in:
parent
d665d9a18c
commit
99f8e10809
|
@ -5,8 +5,7 @@ set +x -eu -o pipefail
|
||||||
readonly thisDir="$(cd $(dirname ${BASH_SOURCE[0]}); pwd)"
|
readonly thisDir="$(cd $(dirname ${BASH_SOURCE[0]}); pwd)"
|
||||||
readonly aioDir="$(realpath $thisDir/..)"
|
readonly aioDir="$(realpath $thisDir/..)"
|
||||||
|
|
||||||
readonly appPtorConf="$aioDir/tests/e2e/protractor.conf.js"
|
readonly protractorConf="$aioDir/tests/deployment/e2e/protractor.conf.js"
|
||||||
readonly cfgPtorConf="$aioDir/tests/deployment/e2e/protractor.conf.js"
|
|
||||||
readonly minPwaScore="95"
|
readonly minPwaScore="95"
|
||||||
readonly urls=(
|
readonly urls=(
|
||||||
"https://angular.io/"
|
"https://angular.io/"
|
||||||
|
@ -24,11 +23,8 @@ set +x -eu -o pipefail
|
||||||
for url in "${urls[@]}"; do
|
for url in "${urls[@]}"; do
|
||||||
echo -e "\nChecking '$url'...\n-----"
|
echo -e "\nChecking '$url'...\n-----"
|
||||||
|
|
||||||
# Run e2e tests.
|
# Run basic e2e and deployment config tests.
|
||||||
yarn protractor "$appPtorConf" --baseUrl "$url"
|
yarn protractor "$protractorConf" --baseUrl "$url"
|
||||||
|
|
||||||
# Run deployment config tests.
|
|
||||||
yarn protractor "$cfgPtorConf" --baseUrl "$url"
|
|
||||||
|
|
||||||
# Run PWA-score tests.
|
# Run PWA-score tests.
|
||||||
yarn test-pwa-score "$url" "$minPwaScore"
|
yarn test-pwa-score "$url" "$minPwaScore"
|
||||||
|
|
|
@ -1,31 +1,18 @@
|
||||||
import { browser } from 'protractor';
|
import { browser } from 'protractor';
|
||||||
|
import { SitePage } from './site.po';
|
||||||
|
|
||||||
describe(browser.baseUrl, () => {
|
describe(browser.baseUrl, () => {
|
||||||
const sitemapUrls = browser.params.sitemapUrls;
|
const page = new SitePage();
|
||||||
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));
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeAll(async done => {
|
beforeAll(done => page.init().then(done));
|
||||||
// Make an initial request to unregister the ServiceWorker.
|
|
||||||
await goTo(browser.baseUrl);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => browser.waitForAngularEnabled(false));
|
beforeEach(() => browser.waitForAngularEnabled(false));
|
||||||
afterEach(() => browser.waitForAngularEnabled(true));
|
afterEach(() => browser.waitForAngularEnabled(true));
|
||||||
|
|
||||||
describe('(with sitemap URLs)', () => {
|
describe('(with sitemap URLs)', () => {
|
||||||
sitemapUrls.forEach((url, i) => {
|
page.sitemapUrls.forEach((url, i) => {
|
||||||
it(`should not redirect '${url}' (${i + 1}/${sitemapUrls.length})`, async () => {
|
it(`should not redirect '${url}' (${i + 1}/${page.sitemapUrls.length})`, async () => {
|
||||||
await goTo(url);
|
await page.goTo(url);
|
||||||
|
|
||||||
const expectedUrl = browser.baseUrl + url;
|
const expectedUrl = browser.baseUrl + url;
|
||||||
const actualUrl = (await browser.getCurrentUrl()).replace(/\?.*$/, '');
|
const actualUrl = (await browser.getCurrentUrl()).replace(/\?.*$/, '');
|
||||||
|
@ -36,9 +23,9 @@ describe(browser.baseUrl, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('(with legacy URLs)', () => {
|
describe('(with legacy URLs)', () => {
|
||||||
legacyUrls.forEach(([fromUrl, toUrl], i) => {
|
page.legacyUrls.forEach(([fromUrl, toUrl], i) => {
|
||||||
it(`should redirect '${fromUrl}' to '${toUrl}' (${i + 1}/${legacyUrls.length})`, async () => {
|
it(`should redirect '${fromUrl}' to '${toUrl}' (${i + 1}/${page.legacyUrls.length})`, async () => {
|
||||||
await goTo(fromUrl);
|
await page.goTo(fromUrl);
|
||||||
|
|
||||||
const expectedUrl = (/^http/.test(toUrl) ? '' : browser.baseUrl.replace(/\/$/, '')) + toUrl;
|
const expectedUrl = (/^http/.test(toUrl) ? '' : browser.baseUrl.replace(/\/$/, '')) + toUrl;
|
||||||
const actualUrl = (await browser.getCurrentUrl()).replace(/\?.*$/, '');
|
const actualUrl = (await browser.getCurrentUrl()).replace(/\?.*$/, '');
|
||||||
|
|
|
@ -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<string>(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('');
|
||||||
|
}
|
||||||
|
}
|
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue