Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
933fcae
Add support for Percy
amaanbs Dec 7, 2023
a007216
es-lint fixes
amaanbs Dec 7, 2023
c85e2b1
binary download fix
amaanbs Dec 8, 2023
d1cba84
Merge branch 'main' of github.com:amaanbs/webdriverio into wdio_percy…
amaanbs Dec 8, 2023
02a7706
resolve merge conflicts
amaanbs Dec 8, 2023
f165eaf
add error handling
amaanbs Dec 10, 2023
4f6d8ca
best platform for caps as objects
amaanbs Dec 10, 2023
ffca74b
minor fix
amaanbs Dec 11, 2023
f7a9ca1
minor fixes
amaanbs Dec 12, 2023
abd6493
added unit tests
amaanbs Dec 13, 2023
3047eb2
ts-lint fixes
amaanbs Dec 13, 2023
b29658a
PR review fixes
amaanbs Dec 13, 2023
497c10f
add package-lock.json
amaanbs Dec 13, 2023
f6ee31b
minor fixes
amaanbs Dec 14, 2023
f067682
Screenshot stabilization + default mode = auto
amaanbs Dec 19, 2023
dafa563
PR review fixes
amaanbs Dec 19, 2023
a7a9397
Merge branch 'main' of github.com:amaanbs/webdriverio into wdio_percy…
amaanbs Dec 19, 2023
42b9e06
Fix test failure import
amaanbs Dec 19, 2023
b8e8755
update tests
amaanbs Dec 19, 2023
7c7b1ab
PR review fixes
amaanbs Dec 21, 2023
6819cf2
Merge branch 'main' of github.com:amaanbs/webdriverio into wdio_percy…
amaanbs Dec 21, 2023
907de46
Merge branch 'main' of github.com:amaanbs/webdriverio into wdio_percy…
rev-doshi Jan 8, 2024
1315406
Merge branch 'main' of github.com:amaanbs/webdriverio into wdio_percy…
rev-doshi Jan 8, 2024
e8d6b94
review changes
rev-doshi Jan 4, 2024
4ab0126
linting fix
rev-doshi Jan 4, 2024
e9279bb
import fix
rev-doshi Jan 4, 2024
7811083
import fix
rev-doshi Jan 4, 2024
08c222d
test fixes and using # prefix for private fields
rev-doshi Jan 8, 2024
52b38d9
Revert "test fixes and using # prefix for private fields"
rev-doshi Jan 8, 2024
a0b80e5
test fix
rev-doshi Jan 8, 2024
b438b1f
review comments fix
rev-doshi Jan 9, 2024
425e941
percy packages changes
rev-doshi Jan 9, 2024
7b07fa3
Merge pull request #3 from rev-doshi/wdio_percy_support_v8
rev-doshi Jan 9, 2024
5d5d033
conflict resolution
rev-doshi Jan 9, 2024
436e132
package-lock fix
rev-doshi Jan 9, 2024
463e3b4
review changes
rev-doshi Jan 10, 2024
7b4503c
review changes
rev-doshi Jan 10, 2024
68e13ce
review changes
rev-doshi Jan 11, 2024
fdfcf82
Merge branch 'main' of github.com:amaanbs/webdriverio into wdio_percy…
rev-doshi Jan 11, 2024
77bc0fe
minor fix
rev-doshi Jan 11, 2024
250ed1d
review changes
rev-doshi Jan 11, 2024
0f01b16
master merge and UT fix
rev-doshi Jan 12, 2024
a84203b
master merge
rev-doshi Jan 12, 2024
d39f702
minor fix
rev-doshi Jan 12, 2024
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
61 changes: 61 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/wdio-browserstack-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
"got": "^12.6.1",
"uuid": "^9.0.0",
"webdriverio": "8.27.2",
"yauzl": "^2.10.0",
"@percy/appium-app": "^2.0.1",
"@percy/selenium-webdriver": "^2.0.3",
"winston-transport": "^4.5.0"
},
"peerDependencies": {
Expand Down
183 changes: 183 additions & 0 deletions packages/wdio-browserstack-service/src/Percy/Percy-Handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import type { Capabilities } from '@wdio/types'
import type { BeforeCommandArgs, AfterCommandArgs } from '@wdio/reporter'

import {
o11yClassErrorHandler,
sleep
} from '../util.js'
import PercyCaptureMap from './PercyCaptureMap.js'

import * as PercySDK from './PercySDK.js'
import { PercyLogger } from './PercyLogger.js'

import { PERCY_DOM_CHANGING_COMMANDS_ENDPOINTS, CAPTURE_MODES } from '../constants.js'

class _PercyHandler {
private _testMetadata: { [key: string]: any } = {}
private _sessionName?: string
private _isPercyCleanupProcessingUnderway?: boolean = false
private _percyScreenshotCounter: any = 0
private _percyDeferredScreenshots: any = []
private _percyScreenshotInterval: any = null
private _percyCaptureMap?: PercyCaptureMap

constructor (
private _percyAutoCaptureMode: string | undefined,
private _browser: WebdriverIO.Browser | WebdriverIO.MultiRemoteBrowser,
private _capabilities: Capabilities.RemoteCapability,
private _isAppAutomate?: boolean,
private _framework?: string
) {
if (!_percyAutoCaptureMode || !CAPTURE_MODES.includes(_percyAutoCaptureMode as string)) {
this._percyAutoCaptureMode = 'auto'
}
}

_setSessionName(name: string) {
this._sessionName = name
}

async teardown () {
await new Promise<void>((resolve) => {
setInterval(() => {
if (this._percyScreenshotCounter === 0) {
resolve()
}
}, 1000)
})
}

async percyAutoCapture(eventName: string | null, sessionName: string | null) {
try {
if (eventName) {
if (!sessionName) {
/* Service doesn't wait for handling of browser commands so the below counter is used in teardown method to delay service exit */
this._percyScreenshotCounter += 1
}

this._percyCaptureMap?.increment(sessionName ? sessionName : (this._sessionName as string), eventName)
await (this._isAppAutomate ? PercySDK.screenshotApp(this._percyCaptureMap?.getName( sessionName ? sessionName : (this._sessionName as string), eventName)) : await PercySDK.screenshot(this._browser, this._percyCaptureMap?.getName( sessionName ? sessionName : (this._sessionName as string), eventName)))
this._percyScreenshotCounter -= 1
}
} catch (err: any) {
this._percyScreenshotCounter -= 1
this._percyCaptureMap?.decrement(sessionName ? sessionName : (this._sessionName as string), eventName as string)
PercyLogger.error(`Error while trying to auto capture Percy screenshot ${err}`)
}
}

async before () {
this._percyCaptureMap = new PercyCaptureMap()
}

deferCapture(sessionName: string, eventName: string | null) {
/* Service doesn't wait for handling of browser commands so the below counter is used in teardown method to delay service exit */
this._percyScreenshotCounter += 1
this._percyDeferredScreenshots.push({ sessionName, eventName })
}

isDOMChangingCommand(args: BeforeCommandArgs): boolean {
/*
Percy screenshots which are to be taken on events such as send keys, element click & screenshot are deferred until
another DOM changing command is seen such that any DOM processing post the previous command is completed
*/
return (
typeof args.method === 'string' && typeof args.endpoint === 'string' &&
(
(
args.method === 'POST' &&
(
PERCY_DOM_CHANGING_COMMANDS_ENDPOINTS.includes(args.endpoint) ||
(
/* click / clear element */
args.endpoint.includes('/session/:sessionId/element') &&
(
args.endpoint.includes('click') ||
args.endpoint.includes('clear')
)
) ||
/* execute script sync / async */
(args.endpoint.includes('/session/:sessionId/execute') && args.body?.script) ||
/* Touch action for Appium */
(args.endpoint.includes('/session/:sessionId/touch'))
)
) ||
( args.method === 'DELETE' && args.endpoint === '/session/:sessionId' )
)
)
}

async cleanupDeferredScreenshots() {
this._isPercyCleanupProcessingUnderway = true
for (const entry of this._percyDeferredScreenshots) {
await this.percyAutoCapture(entry.eventName, entry.sessionName)
}
this._percyDeferredScreenshots = []
this._isPercyCleanupProcessingUnderway = false
}

async browserBeforeCommand (args: BeforeCommandArgs) {
try {
if (!this.isDOMChangingCommand(args)) {
return
}
do {
await sleep(1000)
} while (this._percyScreenshotInterval)
this._percyScreenshotInterval = setInterval(async () => {
if (!this._isPercyCleanupProcessingUnderway) {
clearInterval(this._percyScreenshotInterval)
await this.cleanupDeferredScreenshots()
this._percyScreenshotInterval = null
}
}, 1000)
} catch (err: any) {
PercyLogger.error(`Error while trying to cleanup deferred screenshots ${err}`)
}
}

async browserAfterCommand (args: BeforeCommandArgs & AfterCommandArgs) {
try {
if (!args.endpoint || !this._percyAutoCaptureMode) {
return
}
let eventName = null
const endpoint = args.endpoint as string
if (endpoint.includes('click') && ['click', 'auto'].includes(this._percyAutoCaptureMode as string)) {
eventName = 'click'
} else if (endpoint.includes('screenshot') && ['screenshot', 'auto'].includes(this._percyAutoCaptureMode as string)) {
eventName = 'screenshot'
} else if (endpoint.includes('actions') && ['auto'].includes(this._percyAutoCaptureMode as string)) {
if (args.body && args.body.actions && Array.isArray(args.body.actions) && args.body.actions.length && args.body.actions[0].type === 'key') {
eventName = 'keys'
}
} else if (endpoint.includes('/session/:sessionId/element') && endpoint.includes('value') && ['auto'].includes(this._percyAutoCaptureMode as string)) {
eventName = 'keys'
}
if (eventName) {
this.deferCapture(this._sessionName as string, eventName)
}
} catch (err: any) {
PercyLogger.error(`Error while trying to calculate auto capture parameters ${err}`)
}
}

async afterTest () {
if (this._percyAutoCaptureMode && this._percyAutoCaptureMode === 'testcase') {
await this.percyAutoCapture('testcase', null)
}
}

async afterScenario () {
if (this._percyAutoCaptureMode && this._percyAutoCaptureMode === 'testcase') {
await this.percyAutoCapture('testcase', null)
}
}
}

// https://github.com/microsoft/TypeScript/issues/6543
const PercyHandler: typeof _PercyHandler = o11yClassErrorHandler(_PercyHandler)
type PercyHandler = _PercyHandler

export default PercyHandler

Loading