@applitools/eyes-cypress 3.28.2 → 3.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +28 -2
- package/dist/browser/spec-driver.js +0 -8
- package/dist/expose.js +7 -0
- package/dist/plugin/concurrencyMsg.js +9 -0
- package/dist/plugin/config.js +66 -0
- package/dist/plugin/configParams.js +47 -0
- package/dist/plugin/errorDigest.js +74 -0
- package/dist/plugin/getErrorsAndDiffs.js +31 -0
- package/dist/plugin/handleTestResults.js +38 -0
- package/dist/plugin/hooks.js +43 -0
- package/dist/plugin/index.js +17 -0
- package/dist/plugin/isGlobalHooksSupported.js +10 -0
- package/dist/plugin/pluginExport.js +104 -0
- package/dist/plugin/server.js +117 -0
- package/dist/plugin/webSocket.js +129 -0
- package/index.js +3 -2
- package/package.json +37 -18
- package/src/browser/spec-driver.ts +1 -1
- package/src/expose.ts +71 -0
- package/src/plugin/{concurrencyMsg.js → concurrencyMsg.ts} +1 -1
- package/src/plugin/{config.js → config.ts} +6 -15
- package/src/plugin/{configParams.js → configParams.ts} +1 -3
- package/src/plugin/{errorDigest.js → errorDigest.ts} +34 -38
- package/src/plugin/{getErrorsAndDiffs.js → getErrorsAndDiffs.ts} +3 -8
- package/src/plugin/{handleTestResults.js → handleTestResults.ts} +17 -12
- package/src/plugin/hooks.ts +60 -0
- package/src/plugin/index.ts +37 -0
- package/src/plugin/isGlobalHooksSupported.ts +11 -0
- package/src/plugin/pluginExport.ts +118 -0
- package/src/plugin/{server.js → server.ts} +30 -33
- package/src/plugin/{webSocket.js → webSocket.ts} +34 -23
- package/src/setup/handlePlugin.js +21 -2
- package/src/setup/isPluginDefinedESM.js +5 -0
- package/index.d.ts +0 -86
- package/src/plugin/defaultPort.js +0 -1
- package/src/plugin/hooks.js +0 -41
- package/src/plugin/isGlobalHooksSupported.js +0 -13
- package/src/plugin/pluginExport.js +0 -94
- package/src/plugin/startPlugin.js +0 -15
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const {formatters} = require('@applitools/core')
|
|
8
|
-
const {resolve} = require('path')
|
|
1
|
+
import errorDigest from './errorDigest'
|
|
2
|
+
import {makeLogger} from '@applitools/logger'
|
|
3
|
+
import getErrorsAndDiffs from './getErrorsAndDiffs'
|
|
4
|
+
import fs from 'fs'
|
|
5
|
+
import {formatters} from '@applitools/core'
|
|
6
|
+
import {resolve} from 'path'
|
|
9
7
|
|
|
10
|
-
function printTestResults(testResultsArr) {
|
|
8
|
+
function printTestResults(testResultsArr: any) {
|
|
11
9
|
const logger = makeLogger({
|
|
12
10
|
level: testResultsArr.resultConfig.showLogs ? 'info' : 'silent',
|
|
13
11
|
label: 'eyes',
|
|
@@ -26,10 +24,17 @@ function printTestResults(testResultsArr) {
|
|
|
26
24
|
)
|
|
27
25
|
}
|
|
28
26
|
}
|
|
29
|
-
function handleBatchResultsFile(results, tapFileConfig) {
|
|
27
|
+
function handleBatchResultsFile(results: any, tapFileConfig: any) {
|
|
30
28
|
const fileName = tapFileConfig.tapFileName || `${new Date().toISOString()}-eyes.tap`
|
|
31
29
|
const tapFile = resolve(tapFileConfig.tapDirPath, fileName)
|
|
32
|
-
return writeFile(
|
|
30
|
+
return fs.writeFile(
|
|
31
|
+
tapFile,
|
|
32
|
+
formatters.toHierarchicTAPString(results, {includeSubTests: false, markNewAsPassed: true}),
|
|
33
|
+
{},
|
|
34
|
+
(err: any) => {
|
|
35
|
+
if (err) throw err
|
|
36
|
+
},
|
|
37
|
+
)
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
export default {printTestResults, handleBatchResultsFile}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import handleTestResults from './handleTestResults'
|
|
2
|
+
export type EyesCypressAction = 'before:run' | 'after:run'
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
6
|
+
namespace Cypress {
|
|
7
|
+
interface ResolvedConfigOptions {
|
|
8
|
+
appliConfFile: {
|
|
9
|
+
dontCloseBatches: boolean
|
|
10
|
+
batch: any
|
|
11
|
+
serverUrl: string
|
|
12
|
+
proxy: string
|
|
13
|
+
apiKey: string
|
|
14
|
+
batchId: string
|
|
15
|
+
tapDirPath: string
|
|
16
|
+
tapFileName: string
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default function makeGlobalRunHooks({closeManager, closeBatches, closeUniversalServer}: any): {
|
|
23
|
+
'after:run': (results: CypressCommandLine.CypressRunResult) => void | Promise<void>
|
|
24
|
+
'before:run': (runDetails: Cypress.BeforeRunDetails) => void | Promise<void>
|
|
25
|
+
} {
|
|
26
|
+
return {
|
|
27
|
+
'before:run': ({config}: Cypress.BeforeRunDetails): void => {
|
|
28
|
+
if (!(config as Cypress.Config).isTextTerminal) return
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
'after:run': async ({config}: CypressCommandLine.CypressRunResult) => {
|
|
32
|
+
try {
|
|
33
|
+
if (!(config as Cypress.Config).isTextTerminal) return
|
|
34
|
+
const summaries = await closeManager()
|
|
35
|
+
|
|
36
|
+
let testResults
|
|
37
|
+
for (const summary of summaries) {
|
|
38
|
+
testResults = summary.results.map(({testResults}: any) => testResults)
|
|
39
|
+
}
|
|
40
|
+
if (!config.appliConfFile.dontCloseBatches) {
|
|
41
|
+
await closeBatches({
|
|
42
|
+
batchIds: [config.appliConfFile.batchId || config.appliConfFile.batch.id],
|
|
43
|
+
serverUrl: config.appliConfFile.serverUrl,
|
|
44
|
+
proxy: config.appliConfFile.proxy,
|
|
45
|
+
apiKey: config.appliConfFile.apiKey,
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (config.appliConfFile.tapDirPath) {
|
|
50
|
+
await handleTestResults.handleBatchResultsFile(testResults, {
|
|
51
|
+
tapDirPath: config.appliConfFile.tapDirPath,
|
|
52
|
+
tapFileName: config.appliConfFile.tapFileName,
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
} finally {
|
|
56
|
+
await closeUniversalServer()
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
import makePluginExport from './pluginExport'
|
|
3
|
+
import makeConfig from './config'
|
|
4
|
+
import makeStartServer from './server'
|
|
5
|
+
import {makeLogger} from '@applitools/logger'
|
|
6
|
+
|
|
7
|
+
// DON'T REMOVE
|
|
8
|
+
//
|
|
9
|
+
// if remove the `ttsc` will compile the absolute path
|
|
10
|
+
//
|
|
11
|
+
// the absolute path is added because api-extractor goes over the `eyesPlugin`
|
|
12
|
+
// declaration before it goes over the `EyesConfig` definition, and this is why
|
|
13
|
+
// it's important to reverse the order
|
|
14
|
+
export type EyesPluginConfig = {
|
|
15
|
+
tapDirPath: string
|
|
16
|
+
tapFileName: string
|
|
17
|
+
eyesIsDisabled: boolean
|
|
18
|
+
eyesBrowser: any
|
|
19
|
+
eyesLayoutBreakpoints: any
|
|
20
|
+
eyesFailCypressOnDiff: boolean
|
|
21
|
+
eyesDisableBrowserFetching: boolean
|
|
22
|
+
eyesTestConcurrency: number
|
|
23
|
+
eyesWaitBeforeCapture: number
|
|
24
|
+
eyesPort?: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const {config, eyesConfig} = makeConfig()
|
|
28
|
+
const logger = makeLogger({level: config.showLogs ? 'info' : 'silent', label: 'eyes'})
|
|
29
|
+
|
|
30
|
+
const startServer = makeStartServer({logger})
|
|
31
|
+
|
|
32
|
+
const pluginExport = makePluginExport({
|
|
33
|
+
startServer,
|
|
34
|
+
eyesConfig: Object.assign({}, eyesConfig, {appliConfFile: config}),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export default pluginExport
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const CYPRESS_SUPPORTED_VERSION = '6.2.0'
|
|
2
|
+
const CYPRESS_NO_FLAG_VERSION = '6.7.0'
|
|
3
|
+
|
|
4
|
+
export default function isGlobalHooksSupported(config: any) {
|
|
5
|
+
const {version, experimentalRunEvents} = config
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
parseFloat(version) >= parseFloat(CYPRESS_NO_FLAG_VERSION) ||
|
|
9
|
+
(parseFloat(version) >= parseFloat(CYPRESS_SUPPORTED_VERSION) && !!experimentalRunEvents)
|
|
10
|
+
)
|
|
11
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import isGlobalHooksSupported from './isGlobalHooksSupported'
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
import {presult} from '@applitools/functional-commons'
|
|
4
|
+
import makeGlobalRunHooks from './hooks'
|
|
5
|
+
import {type EyesPluginConfig} from './'
|
|
6
|
+
|
|
7
|
+
export default function makePluginExport({startServer, eyesConfig}: any) {
|
|
8
|
+
return function pluginExport(pluginInitArgs: Cypress.ConfigOptions | NodeJS.Module) {
|
|
9
|
+
let eyesServer: any, pluginModuleExports: any, pluginExportsE2E: any, pluginExportsComponent: any
|
|
10
|
+
let pluginExports
|
|
11
|
+
if ((pluginInitArgs as NodeJS.Module).exports) {
|
|
12
|
+
const pluginAsNodeJSModule = pluginInitArgs as NodeJS.Module
|
|
13
|
+
pluginExports =
|
|
14
|
+
pluginAsNodeJSModule.exports && pluginAsNodeJSModule.exports.default
|
|
15
|
+
? pluginAsNodeJSModule.exports.default
|
|
16
|
+
: pluginAsNodeJSModule.exports
|
|
17
|
+
|
|
18
|
+
if (pluginExports.component) {
|
|
19
|
+
pluginExportsComponent = pluginExports.component.setupNodeEvents
|
|
20
|
+
}
|
|
21
|
+
if (pluginExports.e2e) {
|
|
22
|
+
pluginExportsE2E = pluginExports.e2e.setupNodeEvents
|
|
23
|
+
}
|
|
24
|
+
if (!pluginExports.e2e && !pluginExports.component) {
|
|
25
|
+
pluginModuleExports = pluginExports
|
|
26
|
+
}
|
|
27
|
+
if (pluginExports?.component) {
|
|
28
|
+
pluginExports.component.setupNodeEvents = setupNodeEvents
|
|
29
|
+
}
|
|
30
|
+
if (pluginExports?.e2e) {
|
|
31
|
+
pluginExports.e2e.setupNodeEvents = setupNodeEvents
|
|
32
|
+
}
|
|
33
|
+
if (!pluginExports.component && !pluginExports.e2e) {
|
|
34
|
+
if (pluginAsNodeJSModule.exports.default) {
|
|
35
|
+
pluginAsNodeJSModule.exports.default = setupNodeEvents
|
|
36
|
+
} else {
|
|
37
|
+
pluginAsNodeJSModule.exports = setupNodeEvents
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
// this is required because we are currently support cypress < 10
|
|
42
|
+
// in the version before 10 the `e2e.setupNodeEvents` and `component.setupNodeEvents` were not supported
|
|
43
|
+
const pluginAsCypress10PluginOptions = pluginInitArgs as {e2e: {setupNodeEvents: any}; component: {setupNodeEvents: any}}
|
|
44
|
+
if (pluginAsCypress10PluginOptions.component) {
|
|
45
|
+
pluginExportsComponent = pluginAsCypress10PluginOptions.component.setupNodeEvents
|
|
46
|
+
pluginAsCypress10PluginOptions.component.setupNodeEvents = setupNodeEvents
|
|
47
|
+
}
|
|
48
|
+
if (pluginAsCypress10PluginOptions.e2e) {
|
|
49
|
+
pluginExportsE2E = pluginAsCypress10PluginOptions.e2e.setupNodeEvents
|
|
50
|
+
pluginAsCypress10PluginOptions.e2e.setupNodeEvents = setupNodeEvents
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!(pluginInitArgs as NodeJS.Module).exports) {
|
|
55
|
+
return pluginInitArgs
|
|
56
|
+
}
|
|
57
|
+
return function getCloseServer() {
|
|
58
|
+
return eyesServer.close()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function setupNodeEvents(
|
|
62
|
+
origOn: Cypress.PluginEvents,
|
|
63
|
+
cypressConfig: Cypress.PluginConfigOptions,
|
|
64
|
+
): Promise<EyesPluginConfig> {
|
|
65
|
+
const {server, port, closeManager, closeBatches, closeUniversalServer} = await startServer()
|
|
66
|
+
eyesServer = server
|
|
67
|
+
|
|
68
|
+
const globalHooks: any = makeGlobalRunHooks({
|
|
69
|
+
closeManager,
|
|
70
|
+
closeBatches,
|
|
71
|
+
closeUniversalServer,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
if (!pluginModuleExports) {
|
|
75
|
+
pluginModuleExports = cypressConfig.testingType === 'e2e' ? pluginExportsE2E : pluginExportsComponent
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const isGlobalHookCalledFromUserHandlerMap = new Map()
|
|
79
|
+
eyesConfig.eyesIsGlobalHooksSupported = isGlobalHooksSupported(cypressConfig)
|
|
80
|
+
let moduleExportsResult = {}
|
|
81
|
+
// in case setupNodeEvents is not defined in cypress.config file
|
|
82
|
+
if (typeof pluginModuleExports === 'function') {
|
|
83
|
+
moduleExportsResult = await pluginModuleExports(onThatCallsUserDefinedHandler, cypressConfig)
|
|
84
|
+
}
|
|
85
|
+
if (eyesConfig.eyesIsGlobalHooksSupported) {
|
|
86
|
+
for (const [eventName, eventHandler] of Object.entries(globalHooks)) {
|
|
87
|
+
if (!isGlobalHookCalledFromUserHandlerMap.get(eventName)) {
|
|
88
|
+
origOn.call(this, eventName, eventHandler)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return Object.assign({}, eyesConfig, {eyesPort: port}, moduleExportsResult)
|
|
94
|
+
|
|
95
|
+
// This piece of code exists because at the point of writing, Cypress does not support multiple event handlers:
|
|
96
|
+
// https://github.com/cypress-io/cypress/issues/5240#issuecomment-948277554
|
|
97
|
+
// So we wrap Cypress' `on` function in order to wrap the user-defined handler. This way we can call our own handler
|
|
98
|
+
// in addition to the user's handler
|
|
99
|
+
function onThatCallsUserDefinedHandler(eventName: string, handler: any) {
|
|
100
|
+
const isRunEvent = eventName === 'before:run' || eventName === 'after:run'
|
|
101
|
+
let handlerToCall = handler
|
|
102
|
+
if (eyesConfig.eyesIsGlobalHooksSupported && isRunEvent) {
|
|
103
|
+
handlerToCall = handlerThatCallsUserDefinedHandler
|
|
104
|
+
isGlobalHookCalledFromUserHandlerMap.set(eventName, true)
|
|
105
|
+
}
|
|
106
|
+
return origOn.call(this, eventName, handlerToCall)
|
|
107
|
+
|
|
108
|
+
async function handlerThatCallsUserDefinedHandler(...args: any[]) {
|
|
109
|
+
const [err] = await presult(Promise.resolve(globalHooks[eventName].apply(this, args)))
|
|
110
|
+
await handler.apply(this, args)
|
|
111
|
+
if (err) {
|
|
112
|
+
throw err
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -1,38 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import connectSocket, {type SocketWithUniversal} from './webSocket'
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
3
|
+
// @ts-ignore
|
|
4
|
+
import {makeServerProcess} from '@applitools/eyes-universal'
|
|
5
|
+
import handleTestResults from './handleTestResults'
|
|
6
|
+
import path from 'path'
|
|
7
|
+
import fs from 'fs'
|
|
8
|
+
import {lt as semverLt} from 'semver'
|
|
9
|
+
import {Server as HttpsServer} from 'https'
|
|
10
|
+
import {Server as WSServer} from 'ws'
|
|
11
|
+
import which from 'which'
|
|
12
|
+
import {type Logger} from '@applitools/logger'
|
|
13
|
+
import {AddressInfo} from 'net'
|
|
14
|
+
import {promisify} from 'util'
|
|
15
|
+
|
|
16
|
+
export default function makeStartServer({logger}: {logger: Logger}) {
|
|
13
17
|
return async function startServer() {
|
|
14
|
-
const key = fs.readFileSync(path.resolve(__dirname, '
|
|
15
|
-
const cert = fs.readFileSync(path.resolve(__dirname, '
|
|
16
|
-
let port
|
|
17
|
-
|
|
18
|
+
const key = fs.readFileSync(path.resolve(__dirname, '../../src/pem/server.key'))
|
|
19
|
+
const cert = fs.readFileSync(path.resolve(__dirname, '../../src/pem/server.cert'))
|
|
18
20
|
const https = new HttpsServer({
|
|
19
21
|
key,
|
|
20
22
|
cert,
|
|
21
23
|
})
|
|
22
|
-
await https.listen(
|
|
23
|
-
if (err) {
|
|
24
|
-
logger.log('error starting plugin server', err)
|
|
25
|
-
} else {
|
|
26
|
-
logger.log(`plugin server running at port: ${https.address().port}`)
|
|
27
|
-
port = https.address().port
|
|
28
|
-
}
|
|
29
|
-
})
|
|
24
|
+
await promisify(https.listen.bind(https))()
|
|
30
25
|
|
|
26
|
+
const port = (https.address() as AddressInfo).port
|
|
31
27
|
const wss = new WSServer({server: https, path: '/eyes', maxPayload: 254 * 1024 * 1024})
|
|
32
28
|
|
|
33
29
|
wss.on('close', () => https.close())
|
|
34
30
|
|
|
35
|
-
const forkOptions
|
|
31
|
+
const forkOptions: {
|
|
32
|
+
detached: boolean
|
|
33
|
+
execPath?: string
|
|
34
|
+
} = {
|
|
36
35
|
detached: true,
|
|
37
36
|
}
|
|
38
37
|
|
|
@@ -53,13 +52,13 @@ function makeStartServer({logger}) {
|
|
|
53
52
|
portResolutionMode: 'random',
|
|
54
53
|
})
|
|
55
54
|
|
|
56
|
-
const managers = []
|
|
57
|
-
let socketWithUniversal
|
|
55
|
+
const managers: {manager: object; socketWithUniversal: SocketWithUniversal}[] = []
|
|
56
|
+
let socketWithUniversal: SocketWithUniversal
|
|
58
57
|
|
|
59
58
|
wss.on('connection', socketWithClient => {
|
|
60
59
|
socketWithUniversal = connectSocket(`ws://localhost:${universalPort}/eyes`)
|
|
61
60
|
|
|
62
|
-
socketWithUniversal.setPassthroughListener(message => {
|
|
61
|
+
socketWithUniversal.setPassthroughListener((message: string) => {
|
|
63
62
|
logger.log('<== ', message.toString().slice(0, 1000))
|
|
64
63
|
const {name, payload} = JSON.parse(message)
|
|
65
64
|
if (name === 'Core.makeManager') {
|
|
@@ -69,7 +68,7 @@ function makeStartServer({logger}) {
|
|
|
69
68
|
socketWithClient.send(message.toString())
|
|
70
69
|
})
|
|
71
70
|
|
|
72
|
-
socketWithClient.on('message', message => {
|
|
71
|
+
socketWithClient.on('message', (message: string) => {
|
|
73
72
|
const msg = JSON.parse(message)
|
|
74
73
|
logger.log('==> ', message.toString().slice(0, 1000))
|
|
75
74
|
if (msg.name === 'Core.makeSDK') {
|
|
@@ -134,13 +133,11 @@ function makeStartServer({logger}) {
|
|
|
134
133
|
),
|
|
135
134
|
)
|
|
136
135
|
}
|
|
137
|
-
function closeBatches(settings) {
|
|
136
|
+
function closeBatches(settings: any) {
|
|
138
137
|
if (socketWithUniversal)
|
|
139
|
-
return socketWithUniversal.request('Core.closeBatches', {settings}).catch(err => {
|
|
138
|
+
return socketWithUniversal.request('Core.closeBatches', {settings}).catch((err: Error) => {
|
|
140
139
|
logger.log('@@@', err)
|
|
141
140
|
})
|
|
142
141
|
}
|
|
143
142
|
}
|
|
144
143
|
}
|
|
145
|
-
|
|
146
|
-
module.exports = makeStartServer
|
|
@@ -1,26 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import WebSocket from 'ws'
|
|
2
|
+
import {v4 as uuid} from 'uuid'
|
|
3
|
+
import {type Socket} from 'net'
|
|
4
|
+
|
|
5
|
+
export type SocketWithUniversal = {
|
|
6
|
+
setPassthroughListener: (_listener: (_message: any) => void) => void
|
|
7
|
+
once(_type: any, _fn: any): any
|
|
8
|
+
on(_type: any, _fn: any): any
|
|
9
|
+
off(_type: any, _fn: any): any
|
|
10
|
+
disconnect(): void
|
|
11
|
+
ref(): () => boolean
|
|
12
|
+
unref(): () => boolean
|
|
13
|
+
send(_message: any): () => boolean
|
|
14
|
+
request(_name: string, _payload: any): any
|
|
15
|
+
}
|
|
3
16
|
|
|
4
|
-
function connectSocket(url) {
|
|
5
|
-
const socket = new WebSocket(url)
|
|
6
|
-
let passthroughListener
|
|
17
|
+
export default function connectSocket(url: string): SocketWithUniversal {
|
|
18
|
+
const socket: WebSocket & {_socket?: Socket} = new WebSocket(url)
|
|
19
|
+
let passthroughListener: any
|
|
7
20
|
const listeners = new Map()
|
|
8
|
-
const queue = new Set()
|
|
21
|
+
const queue = new Set<any>()
|
|
9
22
|
let isReady = false
|
|
10
23
|
|
|
11
24
|
attach()
|
|
12
25
|
|
|
13
26
|
function attach() {
|
|
14
|
-
if (socket.readyState === WebSocket.CONNECTING) socket.on('open', () => attach(
|
|
27
|
+
if (socket.readyState === WebSocket.CONNECTING) socket.on('open', () => attach())
|
|
15
28
|
else if (socket.readyState === WebSocket.OPEN) {
|
|
16
29
|
isReady = true
|
|
17
|
-
queue.forEach(command => command())
|
|
30
|
+
queue.forEach((command: any) => command())
|
|
18
31
|
queue.clear()
|
|
19
32
|
|
|
20
33
|
socket.on('message', message => {
|
|
21
34
|
const {name, key, payload} = deserialize(message)
|
|
22
|
-
const fns = listeners.get(name)
|
|
23
|
-
const keyListeners = key && listeners.get(`${name}/${key}`)
|
|
35
|
+
const fns: any[] = listeners.get(name)
|
|
36
|
+
const keyListeners: any[] = key && listeners.get(`${name}/${key}`)
|
|
24
37
|
if (fns) fns.forEach(fn => fn(payload, key))
|
|
25
38
|
if (keyListeners) keyListeners.forEach(fn => fn(payload, key))
|
|
26
39
|
|
|
@@ -39,18 +52,18 @@ function connectSocket(url) {
|
|
|
39
52
|
queue.clear()
|
|
40
53
|
}
|
|
41
54
|
|
|
42
|
-
function setPassthroughListener(fn) {
|
|
55
|
+
function setPassthroughListener(fn: any) {
|
|
43
56
|
passthroughListener = fn
|
|
44
57
|
}
|
|
45
58
|
|
|
46
|
-
function send(message) {
|
|
59
|
+
function send(message: string) {
|
|
47
60
|
const command = () => socket.send(message)
|
|
48
61
|
if (isReady) command()
|
|
49
62
|
else queue.add(command)
|
|
50
63
|
return () => queue.delete(command)
|
|
51
64
|
}
|
|
52
65
|
|
|
53
|
-
function on(type, fn) {
|
|
66
|
+
function on(type: string | {name: string; key: string}, fn: any) {
|
|
54
67
|
const name = typeof type === 'string' ? type : `${type.name}/${type.key}`
|
|
55
68
|
let fns = listeners.get(name)
|
|
56
69
|
if (!fns) {
|
|
@@ -61,12 +74,12 @@ function connectSocket(url) {
|
|
|
61
74
|
return () => off(name, fn)
|
|
62
75
|
}
|
|
63
76
|
|
|
64
|
-
function once(type, fn) {
|
|
65
|
-
const off = on(type, (...args) => (fn(...args), off()))
|
|
77
|
+
function once(type: any, fn: any) {
|
|
78
|
+
const off = on(type, (...args: any[]) => (fn(...args), off()))
|
|
66
79
|
return off
|
|
67
80
|
}
|
|
68
81
|
|
|
69
|
-
function off(name, fn) {
|
|
82
|
+
function off(name: string, fn: any) {
|
|
70
83
|
if (!fn) return listeners.delete(name)
|
|
71
84
|
const fns = listeners.get(name)
|
|
72
85
|
if (!fns) return false
|
|
@@ -75,15 +88,15 @@ function connectSocket(url) {
|
|
|
75
88
|
return existed
|
|
76
89
|
}
|
|
77
90
|
|
|
78
|
-
function emit(type, payload) {
|
|
91
|
+
function emit(type: any, payload: any) {
|
|
79
92
|
return send(serialize(type, payload))
|
|
80
93
|
}
|
|
81
94
|
|
|
82
|
-
function request(name, payload) {
|
|
95
|
+
function request(name: string, payload: any) {
|
|
83
96
|
return new Promise((resolve, reject) => {
|
|
84
97
|
const key = uuid()
|
|
85
98
|
emit({name, key}, payload)
|
|
86
|
-
once({name, key}, response => {
|
|
99
|
+
once({name, key}, (response: any) => {
|
|
87
100
|
if (response.error) return reject(response.error)
|
|
88
101
|
return resolve(response.result)
|
|
89
102
|
})
|
|
@@ -117,13 +130,11 @@ function connectSocket(url) {
|
|
|
117
130
|
}
|
|
118
131
|
}
|
|
119
132
|
|
|
120
|
-
function serialize(type, payload) {
|
|
133
|
+
function serialize(type: any, payload: any) {
|
|
121
134
|
const message = typeof type === 'string' ? {name: type, payload} : {name: type.name, key: type.key, payload}
|
|
122
135
|
return JSON.stringify(message)
|
|
123
136
|
}
|
|
124
137
|
|
|
125
|
-
function deserialize(message) {
|
|
138
|
+
function deserialize(message: any) {
|
|
126
139
|
return JSON.parse(message)
|
|
127
140
|
}
|
|
128
|
-
|
|
129
|
-
module.exports = connectSocket
|
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const chalk = require('chalk')
|
|
4
|
+
const boxen = require('boxen')
|
|
4
5
|
const {addEyesCypressPlugin} = require('./addEyesCypressPlugin')
|
|
5
6
|
const isPluginDefined = require('./isPluginDefined')
|
|
7
|
+
const isPluginDefinedESM = require('./isPluginDefinedESM')
|
|
6
8
|
const fs = require('fs')
|
|
7
9
|
|
|
8
10
|
function handlePlugin(pluginsFilePath) {
|
|
9
11
|
const fileContent = fs.readFileSync(pluginsFilePath, 'utf-8')
|
|
10
|
-
|
|
11
|
-
if (!isPluginDefined(fileContent)) {
|
|
12
|
+
const isESMOrTS = fileContent.indexOf('module.export') === -1
|
|
13
|
+
if (!isESMOrTS && !isPluginDefined(fileContent)) {
|
|
12
14
|
fs.writeFileSync(pluginsFilePath, addEyesCypressPlugin(fileContent))
|
|
13
15
|
console.log(chalk.cyan('Plugins defined.'))
|
|
16
|
+
} else if (isESMOrTS && !isPluginDefinedESM(fileContent)) {
|
|
17
|
+
console.log(
|
|
18
|
+
boxen(
|
|
19
|
+
`
|
|
20
|
+
We detected that you are using TS or ESM syntax. Please configure the plugin as follows:
|
|
21
|
+
|
|
22
|
+
${chalk.green.bold('import eyesPlugin from "@applitools/eyes-cypress"')}
|
|
23
|
+
|
|
24
|
+
export default ${chalk.green.bold('eyesPlugin(')}definedConfig({
|
|
25
|
+
//...
|
|
26
|
+
})${chalk.green.bold(')')}
|
|
27
|
+
|
|
28
|
+
For more information, visit Eyes-Cypress documentation https://www.npmjs.com/package/@applitools/eyes-cypress (manual configuration section)
|
|
29
|
+
`,
|
|
30
|
+
{padding: 1, borderColor: 'cyan'},
|
|
31
|
+
),
|
|
32
|
+
)
|
|
14
33
|
} else {
|
|
15
34
|
console.log(chalk.cyan('Plugins already defined'))
|
|
16
35
|
}
|
package/index.d.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/// <reference types="cypress" />
|
|
2
|
-
import type * as api from '@applitools/eyes-api'
|
|
3
|
-
|
|
4
|
-
type MaybeArray<T> = T | T[]
|
|
5
|
-
|
|
6
|
-
type LegacyRegion = {left: number; top: number; width: number; height: number}
|
|
7
|
-
type Selector = {selector: string; type?: 'css' | 'xpath', nodeType?: 'element' | 'shadow-root'} | 'string'
|
|
8
|
-
type Element = HTMLElement | JQuery<HTMLElement>
|
|
9
|
-
type ElementWithOptions = {element: Element, regionId?: string, padding?: any}
|
|
10
|
-
|
|
11
|
-
interface CypressCheckSettings extends api.CheckSettingsAutomationPlain<Element, Selector>{
|
|
12
|
-
tag?: CypressCheckSettings['name']
|
|
13
|
-
|
|
14
|
-
target?: 'window' | 'region'
|
|
15
|
-
selector?: Selector
|
|
16
|
-
element?: Element
|
|
17
|
-
|
|
18
|
-
ignore?: MaybeArray<NonNullable<CypressCheckSettings['ignoreRegions']>[number] | LegacyRegion | ElementWithOptions>
|
|
19
|
-
layout?: MaybeArray<NonNullable<CypressCheckSettings['layoutRegions']>[number] | LegacyRegion| ElementWithOptions>
|
|
20
|
-
content?: MaybeArray<NonNullable<CypressCheckSettings['contentRegions']>[number] | LegacyRegion| ElementWithOptions>
|
|
21
|
-
strict?: MaybeArray<NonNullable<CypressCheckSettings['strictRegions']>[number] | LegacyRegion | ElementWithOptions>
|
|
22
|
-
floating?: MaybeArray<NonNullable<CypressCheckSettings['floatingRegions']>[number] | ((ElementWithOptions | Selector | LegacyRegion) & {maxUpOffset?: number; maxDownOffset?: number; maxLeftOffset?: number; maxRightOffset?: number})>
|
|
23
|
-
accessibility?: MaybeArray<NonNullable<CypressCheckSettings['accessibilityRegions']>[number] | ((ElementWithOptions | Selector | LegacyRegion) & {accessibilityType?: api.AccessibilityRegionTypePlain})>
|
|
24
|
-
scriptHooks?: CypressCheckSettings['hooks']
|
|
25
|
-
ignoreCaret?: boolean
|
|
26
|
-
ignoreDisplacements?: boolean
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface CypressEyesConfig extends api.ConfigurationPlain<Element, Selector> {
|
|
30
|
-
browser?: MaybeArray<NonNullable<CypressEyesConfig['browsersInfo']>[number] | {deviceName: string; screenOrientation?: api.ScreenOrientationPlain; name?: string}>
|
|
31
|
-
|
|
32
|
-
batchId?: NonNullable<CypressEyesConfig['batch']>['id']
|
|
33
|
-
batchName?: NonNullable<CypressEyesConfig['batch']>['name']
|
|
34
|
-
batchSequence?: NonNullable<CypressEyesConfig['batch']>['sequenceName']
|
|
35
|
-
notifyOnCompletion?: NonNullable<CypressEyesConfig['batch']>['notifyOnCompletion']
|
|
36
|
-
|
|
37
|
-
envName?: CypressEyesConfig['environmentName']
|
|
38
|
-
|
|
39
|
-
accessibilitySettings?: NonNullable<CypressEyesConfig['defaultMatchSettings']>['accessibilitySettings']
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
declare global {
|
|
43
|
-
namespace Cypress {
|
|
44
|
-
interface Chainable {
|
|
45
|
-
/**
|
|
46
|
-
* Create an Applitools test.
|
|
47
|
-
* This will start a session with the Applitools server.
|
|
48
|
-
* @example
|
|
49
|
-
* cy.eyesOpen({ appName: 'My App' })
|
|
50
|
-
*/
|
|
51
|
-
eyesOpen(config?: CypressEyesConfig): null
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Generate a screenshot of the current page and add it to the Applitools Test.
|
|
55
|
-
* @example
|
|
56
|
-
* cy.eyesCheckWindow()
|
|
57
|
-
*
|
|
58
|
-
* OR
|
|
59
|
-
*
|
|
60
|
-
* cy.eyesCheckWindow({
|
|
61
|
-
* target: 'region',
|
|
62
|
-
* selector: '.my-element'
|
|
63
|
-
* });
|
|
64
|
-
*/
|
|
65
|
-
eyesCheckWindow(tag?: string): null
|
|
66
|
-
eyesCheckWindow(settings?: CypressCheckSettings): null
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Close the applitools test and check that all screenshots are valid.
|
|
70
|
-
* @example cy.eyesClose()
|
|
71
|
-
*/
|
|
72
|
-
eyesClose(): null
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Returns an object with the applitools test results from a given test / test file. This should be called after close.
|
|
76
|
-
* @example
|
|
77
|
-
* after(() => {
|
|
78
|
-
* cy.eyesGetAllTestResults().then(summary => {
|
|
79
|
-
* console.log(summary)
|
|
80
|
-
* })
|
|
81
|
-
* })
|
|
82
|
-
*/
|
|
83
|
-
eyesGetAllTestResults(): Chainable<api.TestResultsSummary>
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = 7373
|
package/src/plugin/hooks.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
const handleTestResults = require('./handleTestResults')
|
|
3
|
-
|
|
4
|
-
function makeGlobalRunHooks({closeManager, closeBatches, closeUniversalServer}) {
|
|
5
|
-
return {
|
|
6
|
-
'before:run': ({config}) => {
|
|
7
|
-
if (!config.isTextTerminal) return
|
|
8
|
-
},
|
|
9
|
-
|
|
10
|
-
'after:run': async ({config}) => {
|
|
11
|
-
try {
|
|
12
|
-
if (!config.isTextTerminal) return
|
|
13
|
-
const summaries = await closeManager()
|
|
14
|
-
|
|
15
|
-
let testResults
|
|
16
|
-
for (const summary of summaries) {
|
|
17
|
-
testResults = summary.results.map(({testResults}) => testResults)
|
|
18
|
-
}
|
|
19
|
-
if (!config.appliConfFile.dontCloseBatches) {
|
|
20
|
-
await closeBatches({
|
|
21
|
-
batchIds: [config.appliConfFile.batchId || config.appliConfFile.batch.id],
|
|
22
|
-
serverUrl: config.appliConfFile.serverUrl,
|
|
23
|
-
proxy: config.appliConfFile.proxy,
|
|
24
|
-
apiKey: config.appliConfFile.apiKey,
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (config.appliConfFile.tapDirPath) {
|
|
29
|
-
await handleTestResults.handleBatchResultsFile(testResults, {
|
|
30
|
-
tapDirPath: config.appliConfFile.tapDirPath,
|
|
31
|
-
tapFileName: config.appliConfFile.tapFileName,
|
|
32
|
-
})
|
|
33
|
-
}
|
|
34
|
-
} finally {
|
|
35
|
-
await closeUniversalServer()
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
module.exports = makeGlobalRunHooks
|