import {expectError, expectType} from 'tsd'; import {EnhancedPageObject, PageObjectModel, Awaitable, NightwatchAPI, Cookie, NightwatchLogEntry, NightwatchLogTypes, WindowSizeAndPosition} from '..'; import {WebElement} from 'selenium-webdriver'; // Page object file const fileUploadPageElements = { fileUploadInput: 'input#file-upload', submitButton: 'input#file-submit', uploadFiles: '#uploaded-files' }; const menuSection = { selector: 'nav', elements: { home: 'a[href="/"]', about: 'a[href="/about"]' } }; const fileUploadPage = { url(this: EnhancedPageObject) { return `${this.api.launch_url}/upload`; }, elements: fileUploadPageElements, sections: { menu: menuSection } } satisfies PageObjectModel; export interface FileUploadPage extends EnhancedPageObject<{}, typeof fileUploadPageElements, typeof fileUploadPage.sections, {}, () => string> {} // eslint-disable-line @typescript-eslint/ban-types export default fileUploadPage; // Declare the newly created page object in the NightwatchCustomPageObjects interface. // This will allow you to access the page object type in your test files. declare module '..' { interface NightwatchCustomPageObjects { FileUpload(): FileUploadPage; } interface NightwatchCustomCommands { // uploadFile1 returns NightwatchAPI (breaks chaining on page objects) uploadFile1(selector: string, filePath: string): NightwatchAPI; // uploadFile2 returns Awaitable (breaks chaining on page objects) uploadFile2(selector: string, filePath: string): Awaitable; // uploadFile3 returns 'this' (allows chaining) uploadFile3(selector: string, filePath: string): this; // uploadFile4 returns 'this' (allows chaining) uploadFile4(selector: string, filePath: string): Awaitable; upload: { file(selector: string, filePath: string): NightwatchAPI; } } } // test file describe('File Upload', function() { it('File Upload test', function() { const fileUploadPage = browser.page.FileUpload(); const url = fileUploadPage.url(); fileUploadPage .navigate(url) .uploadFile('@fileUploadInput', 'test.txt') // alternate way of passing an element instead of '@submitButton' .click(fileUploadPage.elements.submitButton) .expect.element('@uploadFiles').text.to.equal('test.txt'); // test custom commands over page object expectType(fileUploadPage.uploadFile1('@fileUploadInput', 'test2.txt')); expectType>(fileUploadPage.uploadFile2('@fileUploadInput', 'test2.txt')); // uploadFile3 returns 'this', so it preserves the page object type expectType(fileUploadPage.uploadFile3('@fileUploadInput', 'test2.txt')); expectType>(fileUploadPage.uploadFile4('@fileUploadInput', 'test2.txt')); // should fail ideally but succeeding (namespace from custom commands not loaded directly into page object??) expectType(fileUploadPage.upload.file('@fileUploadInput', 'test2.txt')); // test custom commands over page object sections const menuSection = fileUploadPage.section.menu; expectType(menuSection.uploadFile1('@fileUploadInput', 'test2.txt')); expectType>(menuSection.uploadFile2('@fileUploadInput', 'test2.txt')); // uploadFile3 returns 'this', so it preserves the section type // Note: TypeScript may not properly resolve 'this' for custom commands on sections // The command works, but type resolution is limited menuSection.uploadFile3('@fileUploadInput', 'test2.txt'); // should fail because the namespaces from custom commands are not loaded directly into the section expectError(menuSection.upload.file('@fileUploadInput', 'test2.txt')); browser.end(); }); }); /************************** * OTHER TESTS *************************/ describe('File Upload 2', function() { it('File Upload test', function() { const fileUploadPage = browser.page.FileUpload(); // user actions element commands work on page objects fileUploadPage.rightClick('@fileUploadInput'); fileUploadPage.doubleClick('@fileUploadInput'); fileUploadPage.clickAndHold('@fileUploadInput'); expectError(fileUploadPage.navigateTo('https://google.com')); expectType(fileUploadPage.url()); // user actions element commands work on sections const menuSection = fileUploadPage.section.menu; menuSection.rightClick('@fileUploadInput'); menuSection.doubleClick('@fileUploadInput'); menuSection.clickAndHold('@fileUploadInput'); expectError(menuSection.navigateTo('https://google.com')); expectError(menuSection.url()); }); }); /************************** * TESTS FOR MOVED COMMANDS (SharedClientCommands) *************************/ describe('Page Object - SharedClientCommands', function() { it('should have access to moved commands from SharedClientCommands', function() { const fileUploadPage = browser.page.FileUpload(); const menuSection = fileUploadPage.section.menu; // Test commands that were moved to SharedClientCommands // Verify they exist, are callable, and return correct types expectType>(fileUploadPage.axeInject()); expectType>(fileUploadPage.axeRun()); expectType>(fileUploadPage.debug()); expectType>(fileUploadPage.deleteCookie('test')); expectType>(fileUploadPage.deleteCookies()); expectType>(fileUploadPage.end()); expectType>(fileUploadPage.getCookie('test-cookie')); expectType>(fileUploadPage.getCookies()); expectType>(fileUploadPage.getLog('server')); expectType>(fileUploadPage.getLogTypes()); expectType>(fileUploadPage.getTitle()); expectType>(fileUploadPage.getWindowPosition()); expectType>(fileUploadPage.getWindowRect()); expectType>(fileUploadPage.getWindowSize()); expectType>(fileUploadPage.init()); expectType>(fileUploadPage.injectScript('script.js')); expectType>(fileUploadPage.isLogAvailable('browser')); expectType>(fileUploadPage.maximizeWindow()); expectType>(fileUploadPage.pageSource()); expectType>(fileUploadPage.pause(1000)); expectType>(fileUploadPage.perform(() => {})); expectType>(fileUploadPage.perform(() => {})); expectType>(fileUploadPage.resizeWindow(1000, 800)); expectType>(fileUploadPage.saveScreenshot('test.png')); expectType>(fileUploadPage.setCookie({name: 'test', value: 'value'})); expectType>(fileUploadPage.setWindowPosition(0, 0)); expectType>(fileUploadPage.setWindowRect({x: 0, y: 0, width: 800, height: 600})); expectType>(fileUploadPage.setWindowSize(800, 600)); expectType>(fileUploadPage.urlHash('#test')); expectType>(fileUploadPage.useCss()); expectType>(fileUploadPage.useXpath()); // These should also work on sections with correct return types expectType>(menuSection.axeInject()); expectType>(menuSection.axeRun()); expectType>(menuSection.debug()); expectType>(menuSection.deleteCookie('test')); expectType>(menuSection.deleteCookies()); expectType>(menuSection.end()); expectType>(menuSection.getCookie('test-cookie')); expectType>(menuSection.getCookies()); expectType>(menuSection.getLog('server')); expectType>(menuSection.getLogTypes()); expectType>(menuSection.getTitle()); expectType>(menuSection.getWindowPosition()); expectType>(menuSection.getWindowRect()); expectType>(menuSection.getWindowSize()); expectType>(menuSection.init()); expectType>(menuSection.injectScript('script.js')); expectType>(menuSection.isLogAvailable('browser')); expectType>(menuSection.maximizeWindow()); expectType>(menuSection.pageSource()); expectType>(menuSection.pause(1000)); expectType>(menuSection.perform(() => {})); expectType>(menuSection.perform(() => {})); expectType>(menuSection.resizeWindow(1000, 800)); expectType>(menuSection.saveScreenshot('test.png')); expectType>(menuSection.setCookie({name: 'test', value: 'value'})); expectType>(menuSection.setWindowPosition(0, 0)); expectType>(menuSection.setWindowRect({x: 0, y: 0, width: 800, height: 600})); expectType>(menuSection.setWindowSize(800, 600)); expectType>(menuSection.urlHash('#test')); expectType>(menuSection.useCss()); expectType>(menuSection.useXpath()); }); }); /************************** * TESTS FOR CHROMIUM CLIENT COMMANDS *************************/ describe('Page Object - ChromiumClientCommands', function() { it('should have access to commands from ChromiumClientCommands', function() { const fileUploadPage = browser.page.FileUpload(); const menuSection = fileUploadPage.section.menu; // Test commands from ChromiumClientCommands // Verify they exist, are callable, and return correct types expectType>(fileUploadPage.setGeolocation({latitude: 35.689487, longitude: 139.691706, accuracy: 100})); expectType>(fileUploadPage.setGeolocation()); expectType>(fileUploadPage.setDeviceDimensions({width: 400, height: 600, deviceScaleFactor: 50, mobile: true})); expectType>(fileUploadPage.setDeviceDimensions()); expectType>(fileUploadPage.getPerformanceMetrics()); expectType>(fileUploadPage.enablePerformanceMetrics(true)); expectType>(fileUploadPage.enablePerformanceMetrics()); expectType>(fileUploadPage.takeHeapSnapshot('./snap.heapsnapshot')); expectType>(fileUploadPage.takeHeapSnapshot()); expectType>(fileUploadPage.registerBasicAuth('test-user', 'test-pass')); expectType>(fileUploadPage.captureNetworkRequests(() => {})); expectType>(fileUploadPage.mockNetworkResponse('https://example.com', {status: 200})); expectType>(fileUploadPage.setNetworkConditions({offline: false, latency: 3000, download_throughput: 500 * 1024, upload_throughput: 500 * 1024})); expectType>(fileUploadPage.captureBrowserConsoleLogs(() => {})); expectType>(fileUploadPage.captureBrowserExceptions(() => {})); // These should also work on sections with correct return types expectType>(menuSection.setGeolocation({latitude: 35.689487, longitude: 139.691706, accuracy: 100})); expectType>(menuSection.setGeolocation()); expectType>(menuSection.setDeviceDimensions({width: 400, height: 600, deviceScaleFactor: 50, mobile: true})); expectType>(menuSection.setDeviceDimensions()); expectType>(menuSection.getPerformanceMetrics()); expectType>(menuSection.enablePerformanceMetrics(true)); expectType>(menuSection.enablePerformanceMetrics()); expectType>(menuSection.takeHeapSnapshot('./snap.heapsnapshot')); expectType>(menuSection.takeHeapSnapshot()); expectType>(menuSection.registerBasicAuth('test-user', 'test-pass')); expectType>(menuSection.captureNetworkRequests(() => {})); expectType>(menuSection.mockNetworkResponse('https://example.com', {status: 200})); expectType>(menuSection.setNetworkConditions({offline: false, latency: 3000, download_throughput: 500 * 1024, upload_throughput: 500 * 1024})); expectType>(menuSection.captureBrowserConsoleLogs(() => {})); expectType>(menuSection.captureBrowserExceptions(() => {})); }); }); /************************** * TESTS FOR CLIENT COMMANDS *************************/ describe('Page Object - ClientCommands', function() { it('should verify that ClientCommands-specific commands are not available on page objects', function() { const fileUploadPage = browser.page.FileUpload(); const menuSection = fileUploadPage.section.menu; // Test commands that are specific to ClientCommands (not in ChromiumClientCommands or SharedClientCommands) // These commands are NOT available on page objects as they're not part of PageObjectClientCommands // Verify they don't exist using expectError expectError(fileUploadPage.closeWindow()); expectError(fileUploadPage.fullscreenWindow()); expectError(fileUploadPage.minimizeWindow()); expectError(fileUploadPage.openNewWindow()); expectError(fileUploadPage.getCurrentUrl()); expectError(fileUploadPage.navigateTo('https://example.com')); expectError(fileUploadPage.quit()); expectError(fileUploadPage.waitUntil(function() { return true })); expectError(fileUploadPage.switchWindow('handle')); expectError(fileUploadPage.switchToWindow('handle')); // These should also not be available on sections expectError(menuSection.closeWindow()); expectError(menuSection.fullscreenWindow()); expectError(menuSection.minimizeWindow()); expectError(menuSection.openNewWindow()); expectError(menuSection.getCurrentUrl()); expectError(menuSection.navigateTo('https://example.com')); expectError(menuSection.quit()); expectError(menuSection.waitUntil(function() { return true })); expectError(menuSection.switchWindow('handle')); expectError(menuSection.switchToWindow('handle')); }); }); /************************** * TESTS FOR CUSTOM COMMAND CHAINING *************************/ describe('Page Object - Custom Command Chaining', function() { it('should support chaining custom commands with built-in commands on page objects', function() { const fileUploadPage = browser.page.FileUpload(); // Chain built-in command -> custom command expectType(fileUploadPage.pause(1000).uploadFile1('@fileUploadInput', 'test.txt')); expectType(fileUploadPage.click('@submitButton').uploadFile1('@fileUploadInput', 'test.txt')); expectType(fileUploadPage.setValue('@fileUploadInput', 'value').uploadFile1('@fileUploadInput', 'test.txt')); // Chain custom command -> built-in command // uploadFile1 returns NightwatchAPI, then these commands return Awaitable expectType>(fileUploadPage.uploadFile1('@fileUploadInput', 'test.txt').pause(1000)); expectType>(fileUploadPage.uploadFile1('@fileUploadInput', 'test.txt').click('@submitButton')); expectType>(fileUploadPage.uploadFile1('@fileUploadInput', 'test.txt').getTitle()); // Chain multiple custom commands (uploadFile1 returns NightwatchAPI) expectType(fileUploadPage.uploadFile1('@fileUploadInput', 'test.txt').uploadFile1('@fileUploadInput', 'test2.txt')); // When chained with pause, pause returns Awaitable expectType>(fileUploadPage.uploadFile1('@fileUploadInput', 'test.txt').uploadFile1('@fileUploadInput', 'test2.txt').pause(1000)); // Chain multiple custom commands (uploadFile3 returns 'this', preserves page object type) // uploadFile3 returns FileUploadPage, so chaining multiple returns FileUploadPage expectType(fileUploadPage.uploadFile3('@fileUploadInput', 'test.txt')); expectType>(fileUploadPage.uploadFile4('@fileUploadInput', 'test.txt')); expectType(fileUploadPage.uploadFile4('@fileUploadInput', 'test.txt').uploadFile3('@fileUploadInput', 'test2.txt')); expectType>(fileUploadPage.uploadFile3('@fileUploadInput', 'test.txt').uploadFile4('@fileUploadInput', 'test2.txt')); // When chained with pause, pause returns Awaitable expectType>(fileUploadPage.uploadFile3('@fileUploadInput', 'test.txt').uploadFile3('@fileUploadInput', 'test2.txt').pause(1000)); // Chain custom command that returns 'this' (allows chaining back to page object) // uploadFile3 returns FileUploadPage, then pause returns Awaitable expectType>(fileUploadPage.pause(1000).uploadFile3('@fileUploadInput', 'test.txt').pause(1000)); expectType>(fileUploadPage.uploadFile3('@fileUploadInput', 'test.txt').pause(1000)); // click returns Awaitable expectType>(fileUploadPage.uploadFile3('@fileUploadInput', 'test.txt').click('@submitButton')); expectType>(fileUploadPage.uploadFile3('@fileUploadInput', 'test.txt').getTitle()); // Chain custom command with Awaitable return type -> built-in command expectType>(fileUploadPage.uploadFile2('@fileUploadInput', 'test.txt').click('@submitButton')); // Chain built-in command -> custom command with Awaitable return type expectType>(fileUploadPage.pause(1000).uploadFile2('@fileUploadInput', 'test.txt')); expectType>(fileUploadPage.click('@submitButton').uploadFile2('@fileUploadInput', 'test.txt')); // Chain custom command -> SharedClientCommands (uploadFile1 returns NightwatchAPI) // These commands return Awaitable expectType>(fileUploadPage.uploadFile1('@fileUploadInput', 'test.txt').pause(1000)); expectType>(fileUploadPage.uploadFile1('@fileUploadInput', 'test.txt').debug()); expectType>(fileUploadPage.uploadFile1('@fileUploadInput', 'test.txt').getTitle()); expectType>(fileUploadPage.uploadFile1('@fileUploadInput', 'test.txt').saveScreenshot('test.png')); // Chain custom command -> SharedClientCommands (uploadFile3 returns 'this', preserves page object type) // uploadFile3 returns FileUploadPage, then these commands return Awaitable expectType>(fileUploadPage.uploadFile3('@fileUploadInput', 'test.txt').pause(1000)); expectType>(fileUploadPage.uploadFile3('@fileUploadInput', 'test.txt').debug()); expectType>(fileUploadPage.uploadFile3('@fileUploadInput', 'test.txt').getTitle()); expectType>(fileUploadPage.uploadFile3('@fileUploadInput', 'test.txt').saveScreenshot('test.png')); // Chain SharedClientCommands -> custom command (uploadFile1 returns NightwatchAPI) expectType(fileUploadPage.pause(1000).uploadFile1('@fileUploadInput', 'test.txt')); expectType(fileUploadPage.debug().uploadFile1('@fileUploadInput', 'test.txt')); expectType(fileUploadPage.getTitle().uploadFile1('@fileUploadInput', 'test.txt')); // Chain SharedClientCommands -> custom command (uploadFile3 returns 'this', preserves page object type) // Note: When chaining from Awaitable, TypeScript may not properly resolve 'this' type // These tests verify that uploadFile3 can be called, but type resolution may be limited fileUploadPage.pause(1000).uploadFile3('@fileUploadInput', 'test.txt'); fileUploadPage.debug().uploadFile3('@fileUploadInput', 'test.txt'); fileUploadPage.getTitle().uploadFile3('@fileUploadInput', 'test.txt'); // Chain custom command -> ChromiumClientCommands (uploadFile1 returns NightwatchAPI) // setGeolocation returns Awaitable expectType>(fileUploadPage.uploadFile1('@fileUploadInput', 'test.txt').setGeolocation({ latitude: 35.689487, longitude: 139.691706 })); expectType>(fileUploadPage.uploadFile1('@fileUploadInput', 'test.txt').setDeviceDimensions({ width: 400, height: 600 })); // Chain custom command -> ChromiumClientCommands (uploadFile3 returns 'this', preserves page object type) expectType>(fileUploadPage.uploadFile3('@fileUploadInput', 'test.txt').setGeolocation({ latitude: 35.689487, longitude: 139.691706 })); expectType>(fileUploadPage.uploadFile3('@fileUploadInput', 'test.txt').setDeviceDimensions({ width: 400, height: 600 })); // Chain ChromiumClientCommands -> custom command (uploadFile1 returns NightwatchAPI) expectType(fileUploadPage.setGeolocation({latitude: 35.689487, longitude: 139.691706}).uploadFile1('@fileUploadInput', 'test.txt')); expectType(fileUploadPage.setDeviceDimensions({width: 400, height: 600}).uploadFile1('@fileUploadInput', 'test.txt')); // Chain ChromiumClientCommands -> custom command (uploadFile3 returns 'this', preserves page object type) // Note: When chaining from Awaitable, TypeScript may not properly resolve 'this' type // These tests verify that uploadFile3 can be called, but type resolution may be limited fileUploadPage.setGeolocation({latitude: 35.689487, longitude: 139.691706}).uploadFile3('@fileUploadInput', 'test.txt'); fileUploadPage.setDeviceDimensions({width: 400, height: 600}).uploadFile3('@fileUploadInput', 'test.txt'); }); });