UNPKG

nightwatch

Version:

Easy to use Node.js based end-to-end testing solution for web applications using the W3C WebDriver API.

839 lines (701 loc) 24.2 kB
import { expectAssignable, expectError, expectNotType, expectType } from 'tsd'; import { EventEmitter } from 'events'; import { EnhancedPageObject, EnhancedSectionInstance, NightwatchAPI, NightwatchAssertion, NightwatchAssertionsResult, NightwatchBrowser, NightwatchClient, NightwatchClientObject, NightwatchEnsureResult, NightwatchNodeAssertionsResult, NightwatchTests, PageObjectModel, ELEMENT_KEY, JSON_WEB_OBJECT, ElementResult, Awaitable, SectionProperties, ScopedElement, LocateStrategy } from '..'; import { element as elementNamedExport } from '..'; import { WebElement } from 'selenium-webdriver'; function isNightwatchAssertionsResult<T>(result: NightwatchAssertionsResult<T>): T { return result.value; } // // ./tests/general.ts // const testGeneral: NightwatchTests = { 'Demo test Google 1': () => { browser.registerBasicAuth('test-username', 'test-password').navigateTo('https://google.com').pause(1000); // check types on browser.options expectType<string | string[] | undefined>(browser.options.tag_filter); // expect element <body> to be present in 1000ms browser.expect.element('body').to.be.present.before(1000); // expect element <#lst-ib> to have css property 'display' browser.expect.element('#lst-ib').to.have.css('display'); // expect element <body> to have attribute 'class' which contains text 'vasq' browser.expect.element('body').to.have.attribute('class').which.contains('vasq'); browser.expect.element('#hplogo').to.have.attribute('alt').which.startsWith('G').and.endsWith('oogle'); // expect element <#lst-ib> to be an input tag browser.expect.element('#lst-ib').to.be.an('input'); // expect element <#lst-ib> to be visible browser.expect.element('#lst-ib').to.be.visible; browser.end(); }, 'Demo test Google 2': () => { browser .url('https://www.google.com') .waitForElementVisible('body') .setValue('input[type=text]', 'nightwatch') .getElementRect('input[type=text]', (res) => { console.log(res.value); }) .waitForElementVisible('input[name=btnK]') .click('input[name=btnK]') .pause(1000) .assert.containsText('#main', 'Night Watch') .end(); }, 'Demo Nightwatch API commands': () => { expectType<boolean>(browser.isChrome()); expectType<boolean>(browser.isAndroid()); expectType<boolean>(browser.isMobile()); expectType<boolean>(browser.isAppiumClient()); const element_id = browser.WEBDRIVER_ELEMENT_ID; console.log(element_id); const browserName = browser.browserName; console.log(browserName); expectError(() => { browser.WEBDRIVER_ELEMENT_ID = 'some-element-id'; }) expectError(() => { browser.browserName = 'firefox'; }) expectError(browser.element('css selector', 'something', function (result) { if (result.status === 0) { console.log(result.value); } })); browser.elements('css selector', 'something', function (result) { if (result.status === 0) { expectType<string>(result.value[0][ELEMENT_KEY]); } expectType<NightwatchAPI>(this); }); }, 'Demo Nightwatch API commands with async/await': async () => { // backward compatibility to some extent const element = await browser.element('css selector', 'something'); expectType<WebElement>(element); const elements = await browser.elements('css selector', 'something'); expectType<string>(elements[0][ELEMENT_KEY]); // new element api const elem = elementNamedExport('selector'); expectType<ScopedElement>(elem); expectType<WebElement>(await elem); const childChildEle = await elementNamedExport.find('selector').findAll('child-selector').nth(2).find('child-child-selector'); expectType<WebElement>(childChildEle); }, 'Can run accessibility tests': () => { browser .url('https://nightwatchjs.org') .axeInject() .axeRun(['#navBar', 'nav'], { rules: { 'color-contrast': { enabled: false }, region: { enabled: false } }, }, (result) => { if (result.status === 0) { expectType<{[key: string]: any}>(result.value); } }); }, 'step one: navigate to google': () => { browser .url('https://www.google.com') .waitForElementVisible('body', 1000) .setValue('input[type=text]', 'nightwatch') .waitForElementVisible('input[name=btnK]', 1000); }, 'step two: click input': () => { browser.click('input[name=btnK]').pause(1000).assert.containsText('#main', 'Night Watch').end(); }, 'test user defined globals': () => { browser.url(`http://${browser.globals.username}:${browser.globals.password}@example.com`).end(); }, 'Demo test for built-in API commands for working with the Chrome Devtools Protocol': () => { // setGeolocation browser // Set location of Tokyo, Japan .setGeolocation({ latitude: 35.689487, longitude: 139.691706, accuracy: 100, }) .captureNetworkRequests((requestParams) => { console.log('Request URL:', requestParams.request.url); console.log('Request method:', requestParams.request.method); console.log('Request headers:', requestParams.request.headers); }) .navigateTo('https://www.gps-coordinates.net/my-location') .end(); browser .mockNetworkResponse( 'https://www.google.com/', { status: 200, headers: { 'Content-Type': 'UTF-8', }, body: 'Hello there!', }, (res) => { console.log(res); } ) .setDeviceDimensions({ width: 400, height: 600, deviceScaleFactor: 50, mobile: true, }) .navigateTo('https://www.google.com') .end(); browser .enablePerformanceMetrics() .navigateTo('https://www.google.com') .getPerformanceMetrics((metrics) => { console.log(metrics); }); browser.navigateTo('https://www.google.com').takeHeapSnapshot('./snap.heapsnapshot').end(); browser .captureBrowserConsoleLogs((event) => { console.log(event.type, event.timestamp, event.args[0].value); }) .navigateTo(browser.baseUrl) .executeScript(() => { console.error('here'); }, []); }, 'test assert with async/await': async () => { const attributeResult = browser.assert.attributeContains('input[name=q]', 'placeholder', 'Search'); expectType<Awaitable<NightwatchAPI, NightwatchAssertionsResult<string>>>(attributeResult); isNightwatchAssertionsResult<string>(await attributeResult); const cssPropertyResult = browser.assert.cssProperty('input[name=q]', 'classList', 'searchbox'); expectType<Awaitable<NightwatchAPI, NightwatchAssertionsResult<string>>>(cssPropertyResult); isNightwatchAssertionsResult<string | number>(await cssPropertyResult); const domPropertyResult = browser.assert.domPropertyContains('input[name=q]', 'classList', 'searchbox'); expectType<Awaitable<NightwatchAPI, NightwatchAssertionsResult<any>>>(domPropertyResult); isNightwatchAssertionsResult<any>(await domPropertyResult); const elementsCountResult = browser.assert.elementsCount('input', 8); expectType< Awaitable< NightwatchAPI, NightwatchAssertionsResult<JSON_WEB_OBJECT[]> & { WebdriverElementId: string; } > >(elementsCountResult); const elementsCountAwaitedResult = await elementsCountResult; expectType<JSON_WEB_OBJECT[]>(elementsCountAwaitedResult.value); expectType<string>(elementsCountAwaitedResult.WebdriverElementId); const elementPresentResult = browser.assert.elementPresent('input'); expectType<Awaitable<NightwatchAPI, NightwatchAssertionsResult<ElementResult[]>>>(elementPresentResult); isNightwatchAssertionsResult<Array<{ [ELEMENT_KEY]: string }>>(await elementPresentResult); const hasAttributeResult = browser.assert.hasAttribute('input[name=q]', 'placeholder'); expectType<Awaitable<NightwatchAPI, NightwatchAssertionsResult<string>>>(hasAttributeResult); isNightwatchAssertionsResult<string>(await hasAttributeResult); const selectedResult = browser.assert.selected('input[name=q]'); expectType<Awaitable<NightwatchAPI, NightwatchAssertionsResult<boolean>>>(selectedResult); isNightwatchAssertionsResult<boolean>(await selectedResult); const textResult = browser.assert.textMatches('input[name=q]', /^Search/); expectType<Awaitable<NightwatchAPI, NightwatchAssertionsResult<string>>>(textResult); isNightwatchAssertionsResult<string>(await textResult); const urlResult = browser.assert.urlMatches('https://nightwatch.org'); expectType<Awaitable<NightwatchAPI, NightwatchAssertionsResult<string>>>(urlResult); isNightwatchAssertionsResult<string>(await urlResult); }, 'test node assertions with async/await': async () => { const result = browser.assert.strictEqual('nightwatch', 'nightwatch'); expectType<Awaitable<NightwatchAPI, Error | NightwatchNodeAssertionsResult>>(result); expectType<NightwatchNodeAssertionsResult | Error>(await result); }, }; // // ./tests/duckDuckGo.ts // describe('duckduckgo example', function () { it('Search Nightwatch.js and check results', function (browser) { browser .navigateTo('https://duckduckgo.com') .waitForElementVisible('input[name=q]') .sendKeys('input[name=q]', ['Nightwatch.js']) .click('*[type="submit"]') .assert.visible('.results--main') .assert.textContains('.results--main', 'Nightwatch.js'); }); }); // // .tests/native/wikipedia.ts // const wikipediaAppTest: NightwatchTests = { before: (client: NightwatchBrowser) => { client.click(by.xpath('//XCUIElementTypeButton[@name="Skip"]')); }, after: (client: NightwatchAPI) => { client.end(); }, 'Search for BrowserStack': async (client: NightwatchAPI) => { client .useXpath() .click('//XCUIElementTypeSearchField[@name="Search Wikipedia"]') .getOrientation(function (result) { if (result.status === 0) { expectType<'LANDSCAPE' | 'PORTRAIT'>(result.value); } expectType<NightwatchAPI>(this); }) .setOrientation('LANDSCAPE', function (result) { if (result.status === 0) { expectType<'LANDSCAPE' | 'PORTRAIT'>(result.value); } expectType<NightwatchAPI>(this); }) .appium.pressKeyCode(13, 44) .sendKeys('//XCUIElementTypeSearchField[@name="Search Wikipedia"]', 'browserstack') .click('//XCUIElementTypeStaticText[@name="BrowserStack"]') .waitUntil(async function () { // wait for webview context to be available const contexts = await this.appium.getContexts(function (result) { if (result.status === 0) { expectType<string[]>(result.value); } expectType<NightwatchAPI>(this); }); return contexts.length > 1; }, 50000) .perform(async function () { // switch to webview context const contexts = await this.contexts(); const setContextResult = await this.setContext(contexts[1], function (result) { if (result.status === 0) { expectType<null>(result.value); } expectType<NightwatchAPI>(this); }); const currContext = await this.currentContext(function (result) { if (result.status === 0) { expectType<string | null>(result.value); } expectType<NightwatchAPI>(this); }); expectType<string[]>(contexts); expectType<null>(setContextResult); expectType<string | null>(currContext); // switch orientation back to portrait const currOrientation = await client.getOrientation(); const setOrientationResult = await client.setOrientation('PORTRAIT'); expectType<'LANDSCAPE' | 'PORTRAIT'>(currOrientation); expectType<'LANDSCAPE' | 'PORTRAIT'>(setOrientationResult); }) .useCss() .assert.textEquals('.pcs-edit-section-title', 'BrowserStack'); // command run in webview context }, }; // // ./pages/google.ts // const appsSection = { selector: 'div.gb_qc', commands: { clickYoutube(this: EnhancedSectionInstance) { return this.click('@youtube'); }, something(this: EnhancedSectionInstance) { return this.click('@youtube'); }, }, elements: [{ myAccount: '#gb192' }, { youtube: { selector: '#gb36', }, }] } satisfies SectionProperties; const menuSection = { selector: '#gb', locateStrategy: 'css selector', commands: [ { // add section commands here clickApps(this: EnhancedSectionInstance) { return this.click('@appSection'); }, randomCommand(this: EnhancedSectionInstance) { return this.click('@appSection'); }, }, { doSomething(this: EnhancedSectionInstance) { return this.click('@appSection'); }, }, ], elements: { mail: 'a[href="mail"]', images: { selector: 'a[href="imghp"]', }, }, sections: { apps: appsSection, }, } satisfies SectionProperties; const googleCommands = { submit(this: GooglePage) { this.api.pause(1000); return this.waitForElementVisible('@submitButton', 1000) .click('@submitButton') .waitForElementNotPresent('@submitButton'); }, }; const googlePage = { commands: [googleCommands], elements: { searchBar: 'input[type=text]', submitButton: { selector: 'input[name=btnK]', locateStrategy: 'css selector' }, }, sections: { menu: menuSection, }, } satisfies PageObjectModel; // export = googlePage; // const iFrameCommands = { // url(this: EnhancedPageObject) { // return `${this.api.launch_url}/iframe`; // }, // } const iFrame = { elements: [{ iframe: '#mce_0_ifr', hey: undefined }, { textbox: { selector: 'body#tinymce p', } }], commands: { url(this: EnhancedPageObject) { return `${this.api.launch_url}/iframe`; }, }, } satisfies PageObjectModel; // export = iFrame interface GooglePage extends EnhancedPageObject< typeof googleCommands, typeof googlePage.elements, typeof googlePage.sections > {} interface iFramePage extends EnhancedPageObject<typeof iFrame.commands, typeof iFrame.elements> {} declare module '..' { interface NightwatchCustomPageObjects { google(): GooglePage; IFrame(): iFramePage; } } // TODO: fix Page Object types const testPage = { 'Test commands': () => { const google = browser.page.google(); google.setValue('@searchBar', 'nightwatch').submit().assert.titleContains('nightwatch'); expectType<NightwatchAPI>(google.api); expectType<NightwatchClient>(google.client); const result = google .setValue('@searchBar', 'nightwatch') .assert.titleContains('Google'); expectAssignable<GooglePage>(result); expectAssignable<GooglePage>(result.submit()); expectAssignable<GooglePage>(result.cookies.getAll()); // test new element api google.element('@searchBar'); google.element.findAll('@searchBar'); browser.end(); }, 'Test sections': () => { const google = browser.page.google(); expectAssignable<GooglePage>(google.cookies.deleteAll()); expectError(googlePage.window.maximize()); const menuSection = google.section.menu; expectType<string>(menuSection.selector); expectType<LocateStrategy>(menuSection.locateStrategy); google.expect.section('@menu').to.be.visible; google.expect.section(menuSection).to.be.visible; const result = menuSection .assert.visible('@mail') .assert.visible('@images'); expectAssignable<typeof menuSection>(result); menuSection.expect.element('@mail').to.be.visible; menuSection.expect.element('@images').to.be.visible; expectAssignable<typeof menuSection>(menuSection.alerts.accept()); expectType<NightwatchAPI>(menuSection.api); expectType<NightwatchClient>(menuSection.client); menuSection.selector; expectNotType<any>(menuSection.clickApps()); const imagesElement = menuSection.elements.images; expectNotType<any>(imagesElement); const appSection = menuSection.section.apps; appSection.expect.element('@myAccount').to.be.visible; appSection.expect.element('@youtube').to.be.visible; // test for parent property expectType<typeof menuSection>(appSection.parent); expectType<GooglePage>(appSection.parent.parent); expectError(appSection.parent.parent.parent); const youtubeElement = appSection.elements.youtube; expectNotType<any>(youtubeElement); // test new element api menuSection.element('@main'); menuSection.element.findAll('@main').nth(1).find('@images'); expectNotType<any>(appSection.clickYoutube()); browser.end(); }, 'Test assertions on page': () => { const google: GooglePage = browser.page.google(); google .navigate() .assert.title('Google') // deprecated .assert.titleEquals('Google') // new in 2.0 .assert.visible('@searchBar') .assert.strictEqual('Google', 'Google') // node assertion returning NightwatchAPI .assert.not.titleContains('DuckDuckGo') .moveToElement('@searchBar', 1, 1) .setValue('@searchBar', 'nightwatch') .click('@submit'); expectError(google.assert.not.not.elementPresent('@searchbar')) expectError(google.assert.not.strictEqual('nightwatch', 'nightwatch')) browser.end(); }, 'Test iFrame on page': async () => { const iFrame = browser.page.IFrame(); iFrame.navigate(); const frame = await browser.findElement(iFrame.elements.iframe); console.log(frame.getId()); browser.frame(frame.getId()); iFrame.expect.element('@textbox').text.to.equal('Your content goes here.'); browser.end(); }, 'Test passing CSS selector string to frame': () => { const iFrame = browser.page.IFrame(); iFrame.navigate().waitForElementPresent('#mce_0_ifr', 10000); browser.frame('#mce_0_ifr'); iFrame.expect.element('@textbox').text.to.equal('Your content goes here.'); browser.end(); }, 'Test nested page objects': () => { const google = browser.page.subfolder1.subfolder2.subfolder3.google(); }, }; // // ./tests/specific-commands.ts // const testSpecificCommands: NightwatchTests = { executeAsync: () => { browser.executeAsync( (done) => { setTimeout(() => { done(true); }, 500); }, [], (result) => { browser.assert.equal(result.value, true); } ); browser.executeAsync( (arg1: number, arg2: string, done: (result: true) => void) => { setTimeout(() => { done(true); }, 500); }, [1, '2'], (result) => { browser.assert.equal(result.value, true); } ); browser.end(); }, executeAsyncScript: () => { browser.executeAsyncScript( (done) => { setTimeout(() => { done(true); }, 500); }, [], (result) => { browser.assert.equal(result.value, true); } ); browser.executeAsyncScript( (arg1: number, arg2: number, done: (result: boolean) => void) => { setTimeout(() => { done(true); }, 500); }, [1, 2], (result) => { browser.assert.equal(result.value, true); } ); browser.end(); }, }; // // ./commands/localStorageValue.ts // - function based command // function localStorageValueCommand(this: NightwatchAPI, key: string, callback?: (value: string | null) => void) { const self = this; this.execute( // tslint:disable-next-line:only-arrow-functions function (key) { return window.localStorage.getItem(key); }, [key], // arguments array to be passed (result) => { if (result.status) { throw result.value; } if (typeof callback === 'function') { callback.call(self, result.value); } } ); return this; } declare module '..' { interface NightwatchCustomCommands { localStorageValue(key: string, callback?: (value: string | null) => void): this; } } // module.exports.command = resizeCommand; const testCustomCommandFunction = { 'Test command function': () => { browser.localStorageValue('my-key', (result) => { console.log(result); }); }, }; // // ./commands/consoleLog.ts // - class based command // class ConsoleLog extends EventEmitter { command(this: ConsoleLog & NightwatchAPI, message: string, ...args: any[]) { setTimeout(() => { console.log(message, ...args); this.emit('complete'); }, 1); return this; } } declare module '..' { interface NightwatchCustomCommands { consoleLog(message: string, ...args: any[]): this; } } // module.exports = ConsoleLog; const testCustomCommandClass = { 'Test command class': () => { browser.consoleLog('Hello world!'); }, }; // // ./assertions/text.ts // function text(this: NightwatchAssertion<string>, selector: string, expectedText: string, msg?: string) { this.expected = expectedText; this.message = msg || `Element <${selector}> has text ${this.expected}.`; this.pass = (value) => value === expectedText; this.value = (result) => result.value!; this.command = function (callback) { this.api.findElement('css selector', selector, (elementResult) => { if (elementResult.status === 0) { this.api.elementIdText(elementResult.value[ELEMENT_KEY as keyof ElementResult], (textResult) => { callback({ value: textResult.value as string }); }); } }); return this; }; expectType<NightwatchClientObject>(this.client); } // exports.assertion = text; declare module '..' { interface NightwatchCustomAssertions<ReturnType> { text: (selector: string, expectedText: string, msg?: string) => NightwatchAPI; } } const testCustomAssertion = { 'Test custom assertion': () => { browser.assert.text('#checkme', 'Exactly match text'); }, }; // test global element describe('demo element() global', () => { const signupEl = element(by.css('#signupSection')); const loginEl = element('#weblogin'); test('element globals command', async () => { // use elements created with element() to regular nightwatch assertions browser.assert.visible(loginEl); // use elements created with element() to expect assertions browser.expect.element(loginEl).to.be.visible; // retrieve the WebElement instance const loginWebElement = await loginEl.getWebElement(); }); }); // Ensure test it('Ensure demo test', () => { browser .url('https://nightwatchjs.org') .ensure.titleMatches(/Nightwatch.js/) .ensure.elementIsVisible('#index-container'); }); it('Ensure async/await demo test', async () => { const result = await browser .url('https://nightwatchjs.org') .ensure.urlContains('nightwatch') .ensure.titleMatches(/Nightwatch.js/) .ensure.elementIsVisible('#index-container'); function isNightwatchEnsureResult(v: NightwatchEnsureResult) {} function isNull(v: null) {} isNightwatchEnsureResult(result); isNull(result.value); }); // chai expect test it('Chai demo test', () => { const infoElement = element('.info'); expect(infoElement.property('innerHTML')).to.be.a('string').and.to.include('validation code'); }); // Relative locator test // Describe not working describe('sample with relative locators', () => { before((browser) => browser.navigateTo('https://archive.org/account/login')); it('locates password input', () => { const passwordElement = locateWith(By.tagName('input')).below(By.css('input[type=email]')); browser.waitForElementVisible(passwordElement).expect.element(passwordElement).to.be.an('input'); browser.expect.element(passwordElement).attribute('type').equal('password'); }); });