Skip to content

[Nightwatch JS] Convert to TypeScript #68

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions visual-js/.changeset/hot-stingrays-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@saucelabs/nightwatch-sauce-visual-service": minor
---

Refactor to TypeScript
5 changes: 3 additions & 2 deletions visual-js/visual-nightwatch/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
module.exports = {
extends: ['eslint:recommended'],
extends: ['plugin:prettier/recommended'],
root: true,
parserOptions: {
"ecmaVersion": "latest"
ecmaVersion: 'latest',
sourceType: 'module',
},
parser: '@typescript-eslint/parser',
env: {
es6: true,
node: true,
Expand Down
8 changes: 0 additions & 8 deletions visual-js/visual-nightwatch/index.js

This file was deleted.

5 changes: 5 additions & 0 deletions visual-js/visual-nightwatch/nightwatch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"custom_commands_path": "build/nightwatch/commands",
"custom_assertions_path": "build/nightwatch/assertions",
"globals_path": "build/nightwatch/globals.js"
}
25 changes: 21 additions & 4 deletions visual-js/visual-nightwatch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
"name": "@saucelabs/nightwatch-sauce-visual-service",
"description": "Nightwatch service that add visual checks capabilities using Sauce Labs Visual",
"version": "0.5.2",
"main": "index.js",
"types": "types/nightwatch.d.ts",
"main": "build/index.js",
"types": "build/index.d.ts",
"license": "MIT",
"engines": {
"node": "^16.13 || >=18"
},
"files": [
"build"
],
"typeScriptVersion": "3.8.3",
"keywords": [
"seleniumjs",
Expand All @@ -22,18 +25,32 @@
"nightwatch": "^3.3.2"
},
"devDependencies": {
"@types/mocha": "^10.0.6",
"@typescript-eslint/parser": "^7.11.0",
"concurrently": "^8.2.2",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^29.7.0",
"jest-junit-reporter": "^1.1.0",
"prettier": "^2.8.8"
"prettier": "^2.8.8",
"tsup": "^7.2.0"
},
"scripts": {
"lint": "eslint \"{nightwatch,utils}/**/*.js\"",
"lint": "eslint \"src/**/*.ts\"",
"build": "tsc --noEmit && tsup src",
"test-ignored": "jest --collect-coverage"
},
"tsup": {
"dts": "src/index.ts",
"outDir": "./build",
"format": [
"cjs"
],
"noExternal": [
"@apollo/client"
]
},
"publishConfig": {
"access": "public"
}
Expand Down
27 changes: 27 additions & 0 deletions visual-js/visual-nightwatch/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import chalk from 'chalk';
import 'nightwatch';
import { DiffingMethod, DiffStatus } from '@saucelabs/visual';
import { Awaitable } from 'nightwatch';
import { CheckOptions, Ignorable } from './types';

console.log(
`\n${chalk.yellow('Loaded @saucelabs/nightwatch-sauce-visual-service')}\n`,
);

export { DiffStatus, CheckOptions, Ignorable, DiffingMethod };

declare module 'nightwatch' {
interface NightwatchAssertions<ReturnType> {
sauceVisualResults: (
diffStatus: DiffStatus,
expected: number,
) => ReturnType;
}

interface NightwatchCustomCommands {
sauceVisualCheck(
name: string,
options?: CheckOptions,
): Awaitable<this, void>;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
const { DiffStatus, isSkipMode } = require('@saucelabs/visual');
const { getVisualApi, getVisualResults } = require('../../utils/api');
const { VISUAL_BUILD_ID_KEY } = require('../../utils/constants');
import { DiffStatus, VisualConfig, isSkipMode } from '@saucelabs/visual';
import { getVisualApi, getVisualResults } from '../../utils/api';
import { VISUAL_BUILD_ID_KEY } from '../../utils/constants';
import { NightwatchAssertion } from 'nightwatch';

// See https://nightwatchjs.org/guide/extending-nightwatch/adding-custom-assertions.html#define-a-custom-assertion
exports.assertion = function sauceVisualResults(diffStatus, expected, msg) {
export function assertion(
this: NightwatchAssertion<number | string | undefined>,
diffStatus: DiffStatus,
expected: number,
msg: string,
) {
/**
* Returns the expected value of the assertion which is displayed in the case of a failure
*
* @return {string}
* @return
*/
this.expected = function () {
return this.negate ? `not equal '${expected}'` : `equals '${expected}'`;
Expand All @@ -19,7 +25,7 @@ exports.assertion = function sauceVisualResults(diffStatus, expected, msg) {
*
* The message format also takes into account whether the .not negate has been used
*
* @return {{args: [], message: string}}
* @return
*/
this.formatMessage = function () {
const message =
Expand All @@ -36,8 +42,8 @@ exports.assertion = function sauceVisualResults(diffStatus, expected, msg) {

/**
* Given the value, the condition used to evaluate if the assertion is passed
* @param {*} value
* @return {Boolean}
* @param value
* @return
*/
this.evaluate = function (value) {
return value === expected;
Expand All @@ -46,16 +52,16 @@ exports.assertion = function sauceVisualResults(diffStatus, expected, msg) {
/**
* Called with the result object of the command to retrieve the value which is to be evaluated
*
* @param {Object} result
* @return {*}
* @param result
* @return
*/
this.value = function (result = {}) {
return result.value;
};

/**
* The command which is to be executed by the assertion runner; Nightwatch api is available as this.api
* @param {function} callback
* @param callback
*/
this.command = async function (callback) {
// Return only SKIPPED if in skip mode
Expand Down Expand Up @@ -86,11 +92,9 @@ exports.assertion = function sauceVisualResults(diffStatus, expected, msg) {
}

const {
options: {
webdriver: { host, port },
},
options: { webdriver: { host, port } = {} },
} = this.api;
const sauceConfig = {
const sauceConfig: VisualConfig = {
hostname: host,
port: port,
user: process.env.SAUCE_USERNAME,
Expand Down Expand Up @@ -119,4 +123,4 @@ exports.assertion = function sauceVisualResults(diffStatus, expected, msg) {

return this;
};
};
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,47 @@
const EventEmitter = require('events').EventEmitter;
const {
ensureError,
getFullPageConfig,
isSkipMode,
} = require('@saucelabs/visual');
const { parseIgnoreOptions, toIgnoreRegionIn } = require('../../utils/regions');
const { getMetaInfo, getVisualApi } = require('../../utils/api');
const { VISUAL_BUILD_ID_KEY } = require('../../utils/constants');
import { ensureError, getFullPageConfig, isSkipMode } from '@saucelabs/visual';
import { parseIgnoreOptions, toIgnoreRegionIn } from '../../utils/regions';
import { getMetaInfo, getVisualApi } from '../../utils/api';
import { VISUAL_BUILD_ID_KEY } from '../../utils/constants';
import { NightwatchAPI } from 'nightwatch';
import { NightwatchCustomCommandsModel } from 'nightwatch/types/custom-command';
import { CheckOptions, RunnerSettings } from '../../types';
import { Runnable } from 'mocha';

module.exports = class SauceVisualCheck extends EventEmitter {
type APIType = NightwatchAPI & {
capabilities: Record<string, any>;
options: RunnerSettings;
};

export default class SauceVisualCheck implements NightwatchCustomCommandsModel {
// These are used for Cucumber
static featureName = '';
static scenarioName = '';

// Mocha context can be passed, but
// - not for all runners,
// - not always as the third argument
async command(name, optionsArg = {}, mochaContextArg = {}) {
async command(name: string, optionsArg = {}, mochaContextArg = {}) {
console.log(`Checking ${name}`);
if (isSkipMode()) {
console.log(`Checking ${name}: SKIPPED`);
global.skipped = (global.skipped ?? 0) + 1;
this.emit('complete', null);
return;
return null;
}
console.log(`Checking ${name}`);
const nightwatchBrowserObject = this.api;

// @ts-expect-error API doesn't allow us to type the options / extra values from webdriver.
const nightwatchBrowserObject: APIType = this.api as unknown as APIType;
//
// Check if the first argument is a Mocha context
const isMochaContext = (arg) => arg.title && arg.ctx?._runnable?.title;
const [options, mochaContext] = isMochaContext(optionsArg)
? [{}, optionsArg]
: [optionsArg, mochaContextArg];
const isMochaContext = (arg: Runnable | Record<any, any>) =>
arg.title &&
// TODO: Replace with a non-private member / usage.
// @ts-ignore
arg.ctx?._runnable?.title;
const [options, mochaContext] = (
isMochaContext(optionsArg)
? [{}, optionsArg]
: [optionsArg, mochaContextArg]
) as [CheckOptions, Runnable];
//
// Getting the suite and testname from the current test
let module = '';
Expand All @@ -46,6 +57,8 @@ module.exports = class SauceVisualCheck extends EventEmitter {
const suiteName =
mochaContext?.title || SauceVisualCheck.featureName || module || '';
const testName =
// TODO: Replace with a non-private member / usage.
// @ts-ignore
mochaContext?.ctx?._runnable?.title ||
SauceVisualCheck.scenarioName ||
defaultTestName ||
Expand All @@ -64,7 +77,7 @@ module.exports = class SauceVisualCheck extends EventEmitter {
capabilities,
sessionId,
options: {
webdriver: { host, port },
webdriver: { host, port } = {},
sauceVisualService: {
captureDom: globalCaptureDom = false,
fullPage,
Expand All @@ -82,14 +95,15 @@ module.exports = class SauceVisualCheck extends EventEmitter {

if (!buildId) {
nightwatchBrowserObject.assert.fail('No buildId found');
this.emit('complete', null);
return;
return null;
}

const api = getVisualApi(sauceConfig);
const metaInfo = await getMetaInfo(api, sessionId, jobId);

let result = null;
let result: Awaited<
ReturnType<typeof api.createSnapshotFromWebDriver>
> | null = null;

try {
result = await api.createSnapshotFromWebDriver({
Expand All @@ -114,6 +128,6 @@ module.exports = class SauceVisualCheck extends EventEmitter {
nightwatchBrowserObject.assert.fail(errorMessage);
}

this.emit('complete', result);
return result;
}
};
}
Loading