Skip to content

Commit

Permalink
Merging conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxym committed Aug 7, 2023
2 parents c9317de + 8124b17 commit 7c96835
Show file tree
Hide file tree
Showing 70 changed files with 1,520 additions and 998 deletions.
5 changes: 4 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ executors:
node-18:
docker:
- image: cimg/node:18.12
node-20:
docker:
- image: cimg/node:20.0

jobs:
build:
Expand Down Expand Up @@ -63,4 +66,4 @@ workflows:
- build:
matrix:
parameters:
version: ["14", "16", "18"]
version: ["14", "16", "18", "20"]
4 changes: 2 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ charset = utf-8
end_of_line = lf
insert_final_newline = true

[*.js]
[*.{js,ts}]
indent_style = tab
indent_size = 4
indent_size = 2

# This is because npm would rewrite it to spaces anyway
[*.json]
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,27 +38,27 @@ With Suitest you can:
Suitest currently supports the following types of devices:

- TV's and set-top boxes equipped with an infrared port. This includes most of
the currently available Smart TV's like Samsung, LG, Philips, Panasonic, Sony
etc.
the currently available Smart TV's like Samsung, LG, Hisense, Philips, Panasonic, Sony,
Vizio etc.
- TV's, set-top boxes and mobile devices running Android (including FireTV)
- Microsoft Xbox One, Xbox Series S and X and Sony PlayStation 4 / 5 consoles
- Locally installed Chrome, Firefox, Safari and Edge
- Apple tvOS, iOS and iPadOS devices, including simulators
- Roku
- Vizio
- Apple tvOS, iOS and iPadOS devices, including simulators
- Roku TVs, boxes and sticks

Suitest supports automating end-to-end testing of:

- HbbTV / Freeview Play apps
- Samsung Orsay and Tizen apps
- LG NetCast and webOS apps
- LG NetCast and webOS apps
- HTML apps for other TV's or set-top boxes
- Android TV and mobile apps (including FireTV)
- Apple TV (tvOS), iOS and iPadOS apps
- Xbox One and Xbox Series S and X apps
- PlayStation 4 / 5 apps
- Roku apps
- Vizio SmartCast
- VIDAA apps
- Vizio SmartCast apps
- Traditional websites and web apps for desktop browsers.

## Contributing
Expand Down
1 change: 1 addition & 0 deletions config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const overridableFields = [
'tokenId', 'tokenPassword', 'concurrency', 'preset', 'presets', 'deviceId', 'appConfigId', 'inspect', 'inspectBrk',
'logLevel', 'logDir', 'timestamp', 'configFile', 'disallowCrashReports', 'defaultTimeout', 'screenshotDir',
'includeChangelist', 'testLines', 'testErrors', 'networkLogs', 'consoleLogs', // launcher common extra log options
'recordingOption', 'webhookUrl',
];

const serverAddress = process.env[envVars.SUITEST_BE_SERVER] || 'the.suite.st';
Expand Down
11 changes: 9 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {SuspendAppChain} from './typeDefinition/SuspendAppChain';
import {RelativePosition} from './typeDefinition/RelativePositionChain';
import {LaunchMode} from './typeDefinition/constants/LaunchMode';
import {OpenDeepLinkChain} from './typeDefinition/OpenDeepLink';
import {CookieProp} from './typeDefinition/constants/CookieProp';

// --------------- Suitest Interface ---------------------- //

Expand All @@ -53,10 +54,12 @@ declare namespace suitest {
openSession(options: OpenSessionOptions): Promise<OpenSessionResult|SuitestError>;
closeSession(): Promise<object|SuitestError>;
setAppConfig(configId: string, options?: ConfigOverride): Promise<void|SuitestError>;
pairDevice(deviceId: string): Promise<DeviceData|SuitestError>;
pairDevice(deviceId: string, recordingSettings?: {recording?: 'autostart' | 'manualstart' | 'none', webhookUrl?: string}): Promise<DeviceData|SuitestError>;
releaseDevice(): Promise<void|SuitestError>;
startREPL(options?: ReplOptions): Promise<void>;
getAppConfig(): Promise<AppConfiguration|SuitestError>;
startRecording({webhookUrl}?: {webhookUrl: string}): Promise<void|SuitestError>;
stopRecording({discard}?: {discard: boolean}): Promise<void|SuitestError>;

// config
getConfig(): ConfigureOptions;
Expand All @@ -68,6 +71,8 @@ declare namespace suitest {
setLogLevel(testErrors: ConfigureOptions['testErrors']): void;
setLogLevel(networkLogs: ConfigureOptions['networkLogs']): void;
setLogLevel(consoleLogs: ConfigureOptions['consoleLogs']): void;
setRecordingOption(recordingOption: ConfigureOptions['recordingOption']): void;
setWebhookUrl(webhookUrl: ConfigureOptions['webhookUrl']): void;

// subjects
location(): LocationChain;
Expand Down Expand Up @@ -126,7 +131,6 @@ declare namespace suitest {
model: string,
owner: string,
firmware: string,
isShared: boolean,
modelId: string,
platforms: string[],
customName?: string,
Expand Down Expand Up @@ -156,6 +160,7 @@ declare namespace suitest {
DIRECTIONS: Directions;
SCREEN_ORIENTATION: ScreenOrientation;
LAUNCH_MODE: LaunchMode;
COOKIE_PROP: CookieProp;

authContext: AuthContext;
appContext: Context;
Expand Down Expand Up @@ -285,6 +290,8 @@ declare namespace suitest {
testErrors: 'silent'|'normal'|'verbose'|'debug'|'silly';
networkLogs?: 'silent'|'normal'|'verbose'|'debug'|'silly';
consoleLogs?: 'silent'|'normal'|'verbose'|'debug'|'silly';
recordingOption: 'autostart'|'manualstart'|'none';
webhookUrl: string;
disallowCrashReports: boolean;
continueOnFatalError: boolean;
defaultTimeout: number;
Expand Down
5 changes: 4 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ const suitest = new SUITEST_API({exitOnError: true});

// Check if we are in launcher child process, connect to master IPC,
// override config, start session, pair device, set app config
connectToIpcAndBootstrapSession(suitest);
connectToIpcAndBootstrapSession(suitest).catch(function(err) {
suitest.logger.error(err);
process.exit(1);
});

// Export public API
module.exports = suitest;
2 changes: 1 addition & 1 deletion lib/api/endpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const endpoints = {
appConfigById: '/apps/:appId/configs/:configId',
appTestDefinitions: '/apps/:appId/test-definitions',
appTestDefinitionById: '/apps/:appId/versions/:versionId/tests/:testId',
devices: '/devices',
device: '/devices/:deviceId',
testRun: '/test-run',
testRunById: '/test-run/:testRunId',
testRunOnDevice: '/test-run/:testRunId/device/:deviceId',
Expand Down
10 changes: 5 additions & 5 deletions lib/api/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* http request module
*/

const fetch = require('node-fetch');
const {fetch} = require('../utils/fetch');

const {apiUrl} = require('../../config');
const SuitestError = require('../utils/SuitestError');
Expand All @@ -15,10 +15,10 @@ const {captureException} = require('../utils/sentry/Raven');
*
* @param {string|[string, any]} url
* @param {Object|any} requestObject
* @param {Function} onReject - will be invoked if response not ok
* @param {function(Response)} [onFail] - will be invoked if response not ok
* @returns {Promise}
*/
async function request(url, requestObject, onReject) {
async function request(url, requestObject, onFail) {
if (requestObject.body) {
requestObject.body = JSON.stringify(requestObject.body);

Expand Down Expand Up @@ -47,8 +47,8 @@ async function request(url, requestObject, onReject) {
return res.json();
}

if (onReject) {
return Promise.reject(onReject(res));
if (onFail) {
return onFail(res);
}

throw new SuitestError(
Expand Down
22 changes: 21 additions & 1 deletion lib/api/webSockets.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const WS = require('ws');
const {v1: uuid} = require('uuid');
const {path} = require('ramda');
const Raven = require('raven');
const fetch = require('node-fetch');
const {fetch, setUserAgent} = require('../utils/fetch');

const SuitestError = require('../utils/SuitestError');
const texts = require('../texts');
Expand All @@ -31,6 +31,11 @@ const webSocketsFactory = (self) => {
disconnect();
logger.debug('Initializing websocket connection with options:', connectionOps);

if (!connectionOps.headers) {
connectionOps.headers = {};
}
setUserAgent(connectionOps.headers);

ws = new WS(config.wsUrl, connectionOps);

ws.on('message', msg => {
Expand Down Expand Up @@ -163,6 +168,21 @@ const webSocketsFactory = (self) => {
});
});
}
} else if (
path([message.messageId, 'contentType'])(requestPromises) === 'startRecording' &&
message.content.result === 'error'
) {
// Allowing result: 'error' for startRecording command and not interrupting test flow

const messageId = message.messageId;
const res = message.content.response || message.content;
const req = requestPromises[messageId];
const contentMessage = res.error ? res.error : texts.unknownError();

logger.error(getInfoErrorMessage(contentMessage, '', res, ''));
req.resolve(res);

delete requestPromises[messageId];
} else {
handleResponse(message);
}
Expand Down
2 changes: 2 additions & 0 deletions lib/api/wsContentTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const contentTypes = {
testLine: 'testLine',
takeScreenshot: 'takeScreenshot',
getConfiguration: 'getConfiguration',
startRecording: 'startRecording',
stopRecording: 'stopRecording',
};

Object.freeze(contentTypes);
Expand Down
32 changes: 29 additions & 3 deletions lib/chains/cookieChain.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const {
assertComposer,
gettersComposer,
makeToJSONComposer,
withPropertiesComposer,
} = require('../composers');
const {
invalidInputMessage,
Expand All @@ -28,6 +29,13 @@ const {
getRequestType,
} = require('../utils/socketChainHelper');
const {validate, validators} = require('../validation');
const {PROP_COMPARATOR} = require('../constants/comparator');

const internalCookiePropertyToPublic = (prop) => ({
property: prop.property,
type: prop.type || PROP_COMPARATOR.EQUAL,
val: prop.val,
});

const cookieFactory = (classInstance) => {
const toJSON = data => {
Expand All @@ -54,6 +62,11 @@ const cookieFactory = (classInstance) => {
socketMessage.request.condition.type = applyNegation(data.comparator.type, data);
socketMessage.request.condition.val = data.comparator.val;
}

if (data.properties) {
socketMessage.request.condition.type = 'withProperties';
socketMessage.request.condition.properties = data.properties.map(internalCookiePropertyToPublic);
}
}

return socketMessage;
Expand Down Expand Up @@ -95,9 +108,18 @@ const cookieFactory = (classInstance) => {
output.push(timeoutComposer);
}

if (!data.comparator) {
const withPropertiesWasCalled = !!data.properties;
const valueComparatorWasCalled = !!data.comparator;

if (!valueComparatorWasCalled && !withPropertiesWasCalled) {
output.push(
startWithComposer, endWithComposer, containComposer, equalComposer, matchJSComposer, existComposer
startWithComposer,
endWithComposer,
containComposer,
equalComposer,
matchJSComposer,
existComposer,
withPropertiesComposer,
);
}

Expand All @@ -108,7 +130,11 @@ const cookieFactory = (classInstance) => {
const cookieChain = cookieName => {
return makeChain(classInstance, getComposers, {
type: 'cookie',
cookieName: validate(validators.NON_EMPTY_STRING, cookieName, invalidInputMessage('cookie', 'Cookie name')),
cookieName: validate(
validators.NON_EMPTY_STRING,
cookieName,
invalidInputMessage('cookie', 'Cookie name'),
),
});
};

Expand Down
4 changes: 2 additions & 2 deletions lib/chains/positionChain.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ const positionFactory = (classInstance) => {
return makeChain(classInstance, getComposers, {
type: 'position',
coordinates: {
x: validate(validators.ST_VAR_OR_POSITIVE_NUMBER, x, invalidInputMessage('position', 'Position x')),
y: validate(validators.ST_VAR_OR_POSITIVE_NUMBER, y, invalidInputMessage('position', 'Position y')),
x: validate(validators.ST_VAR_NOT_NEGATIVE_NUMBER, x, invalidInputMessage('position', 'Position x')),
y: validate(validators.ST_VAR_NOT_NEGATIVE_NUMBER, y, invalidInputMessage('position', 'Position y')),
},
});
};
Expand Down
2 changes: 1 addition & 1 deletion lib/chains/pressButtonChain.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const pressButtonFactory = (classInstance) => {
),
longPressMs: options.longPressMs !== undefined
? validate(
validators.ST_VAR_OR_POSITIVE_NUMBER,
validators.ST_VAR_NOT_NEGATIVE_NUMBER,
options.longPressMs,
invalidInputMessage('pressButton', 'Invalid longPressMs'),
)
Expand Down
2 changes: 1 addition & 1 deletion lib/chains/sleepChain.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const sleepFactory = (classInstance) => {
const sleepChain = milliseconds => makeChain(classInstance, getComposers, {
type: 'sleep',
milliseconds: validation.validate(
validation.validators.ST_VAR_OR_POSITIVE_NUMBER,
validation.validators.ST_VAR_NOT_NEGATIVE_NUMBER,
milliseconds,
invalidInputMessage('sleep', 'Sleep milliseconds')
),
Expand Down
16 changes: 15 additions & 1 deletion lib/commands/pairDevice.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,20 @@ const SuitestError = require('../utils/SuitestError');
* Pair with device
* @param {Object} instance - main class instance
* @param {string} deviceId - device to connect to
* @param {Object | undefined} recordingSettings - {recording, webhookUrl} object. Optional.
* @returns {ChainablePromise.<DeviceData>}
*/
async function pairDevice({webSockets, authContext, logger, pairedDeviceContext}, deviceId) {
async function pairDevice(
{webSockets, authContext, logger, pairedDeviceContext, config},
deviceId,
recordingSettings
) {
// validate deviceId string to be in uuid format
validate(validators.UUID, deviceId, invalidInputMessage(pairDevice.name, 'Device id'));

const recordingDefault = recordingSettings ? recordingSettings.recording : 'none';
const webhookUrlDefault = recordingSettings ? recordingSettings.webhookUrl : undefined;

const [deviceDetails] = await getDevicesDetails({authContext}, [{device: deviceId}]);

if (!deviceDetails)
Expand All @@ -31,12 +39,18 @@ async function pairDevice({webSockets, authContext, logger, pairedDeviceContext}

const deviceName = deviceDetails.displayName;

const recordingOption = config.recordingOption || recordingDefault;

const webhookUrlOption = config.webhookUrl || webhookUrlDefault;

logger.delayed(connectingToDevice(deviceName, deviceId), 4e3);

// authorize
const authedContent = await authContext.authorizeWs({
type: wsContentTypes.pairDevice,
deviceId,
recording: recordingOption,
webhookUrl: webhookUrlOption,
}, pairDevice.name);
// make ws request
const res = await webSockets.send(authedContent);
Expand Down
Loading

0 comments on commit 7c96835

Please sign in to comment.