Skip to content

Commit 75b139f

Browse files
DavertMikDavertMik
andauthored
implemented path normalization (#5451)
* implemented path normalization * fixed tests * added path check to waiter * imrpved wait* methods * fix: correct waitForFunction argument order in waitCurrentPathEquals * fix: correct waitForFunction argument order in Puppeteer waitCurrentPathEquals --------- Co-authored-by: DavertMik <davert@testomat.io>
1 parent 21e066c commit 75b139f

File tree

5 files changed

+202
-46
lines changed

5 files changed

+202
-46
lines changed

lib/helper/Playwright.js

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import {
2323
clearString,
2424
requireWithFallback,
2525
normalizeSpacesInString,
26+
normalizePath,
27+
resolveUrl,
2628
relativeDir,
2729
} from '../utils.js'
2830
import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
@@ -2434,7 +2436,7 @@ class Playwright extends Helper {
24342436
const currentUrl = await this._getPageUrl()
24352437
const baseUrl = this.options.url || 'http://localhost'
24362438
const actualPath = new URL(currentUrl, baseUrl).pathname
2437-
return equals('url path').assert(path, actualPath)
2439+
return equals('url path').assert(normalizePath(path), normalizePath(actualPath))
24382440
}
24392441

24402442
/**
@@ -2444,7 +2446,7 @@ class Playwright extends Helper {
24442446
const currentUrl = await this._getPageUrl()
24452447
const baseUrl = this.options.url || 'http://localhost'
24462448
const actualPath = new URL(currentUrl, baseUrl).pathname
2447-
return equals('url path').negate(path, actualPath)
2449+
return equals('url path').negate(normalizePath(path), normalizePath(actualPath))
24482450
}
24492451

24502452
/**
@@ -3404,20 +3406,21 @@ class Playwright extends Helper {
34043406
*/
34053407
async waitInUrl(urlPart, sec = null) {
34063408
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
3409+
const expectedUrl = resolveUrl(urlPart, this.options.url)
34073410

34083411
return this.page
34093412
.waitForFunction(
34103413
urlPart => {
34113414
const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
34123415
return currUrl.indexOf(urlPart) > -1
34133416
},
3414-
urlPart,
3417+
expectedUrl,
34153418
{ timeout: waitTimeout },
34163419
)
34173420
.catch(async e => {
3418-
const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
3421+
const currUrl = await this._getPageUrl()
34193422
if (/Timeout/i.test(e.message)) {
3420-
throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
3423+
throw new Error(`expected url to include ${expectedUrl}, but found ${currUrl}`)
34213424
} else {
34223425
throw e
34233426
}
@@ -3429,26 +3432,46 @@ class Playwright extends Helper {
34293432
*/
34303433
async waitUrlEquals(urlPart, sec = null) {
34313434
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
3432-
3433-
const baseUrl = this.options.url
3434-
let expectedUrl = urlPart
3435-
if (urlPart.indexOf('http') < 0) {
3436-
expectedUrl = baseUrl + urlPart
3437-
}
3435+
const expectedUrl = resolveUrl(urlPart, this.options.url)
34383436

34393437
try {
34403438
await this.page.waitForURL(
3441-
url => url.href.includes(expectedUrl),
3439+
url => url.href === expectedUrl,
34423440
{ timeout: waitTimeout },
34433441
)
34443442
} catch (e) {
34453443
const currUrl = await this._getPageUrl()
34463444
if (/Timeout/i.test(e.message)) {
3447-
if (!currUrl.includes(expectedUrl)) {
3448-
throw new Error(`expected url to be ${expectedUrl}, but found ${currUrl}`)
3449-
} else {
3450-
throw new Error(`expected url not loaded, error message: ${e.message}`)
3451-
}
3445+
throw new Error(`expected url to be ${expectedUrl}, but found ${currUrl}`)
3446+
} else {
3447+
throw e
3448+
}
3449+
}
3450+
}
3451+
3452+
/**
3453+
* {{> waitCurrentPathEquals }}
3454+
*/
3455+
async waitCurrentPathEquals(path, sec = null) {
3456+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
3457+
const normalizedPath = normalizePath(path)
3458+
3459+
try {
3460+
await this.page.waitForFunction(
3461+
expectedPath => {
3462+
const actualPath = window.location.pathname
3463+
const normalizePath = p => (p === '' || p === '/' ? '/' : p.replace(/\/+/g, '/').replace(/\/$/, '') || '/')
3464+
return normalizePath(actualPath) === expectedPath
3465+
},
3466+
normalizedPath,
3467+
{ timeout: waitTimeout },
3468+
)
3469+
} catch (e) {
3470+
const currentUrl = await this._getPageUrl()
3471+
const baseUrl = this.options.url || 'http://localhost'
3472+
const actualPath = new URL(currentUrl, baseUrl).pathname
3473+
if (/Timeout/i.test(e.message)) {
3474+
throw new Error(`expected path to be ${normalizedPath}, but found ${normalizePath(actualPath)}`)
34523475
} else {
34533476
throw e
34543477
}

lib/helper/Puppeteer.js

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
isModifierKey,
2727
requireWithFallback,
2828
normalizeSpacesInString,
29+
normalizePath,
30+
resolveUrl,
2931
} from '../utils.js'
3032
import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
3133
import ElementNotFound from './errors/ElementNotFound.js'
@@ -1715,7 +1717,7 @@ class Puppeteer extends Helper {
17151717
const currentUrl = await this._getPageUrl()
17161718
const baseUrl = this.options.url || 'http://localhost'
17171719
const actualPath = new URL(currentUrl, baseUrl).pathname
1718-
return equals('url path').assert(path, actualPath)
1720+
return equals('url path').assert(normalizePath(path), normalizePath(actualPath))
17191721
}
17201722

17211723
/**
@@ -1725,7 +1727,7 @@ class Puppeteer extends Helper {
17251727
const currentUrl = await this._getPageUrl()
17261728
const baseUrl = this.options.url || 'http://localhost'
17271729
const actualPath = new URL(currentUrl, baseUrl).pathname
1728-
return equals('url path').negate(path, actualPath)
1730+
return equals('url path').negate(normalizePath(path), normalizePath(actualPath))
17291731
}
17301732

17311733
/**
@@ -2465,6 +2467,7 @@ class Puppeteer extends Helper {
24652467
*/
24662468
async waitInUrl(urlPart, sec = null) {
24672469
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
2470+
const expectedUrl = resolveUrl(urlPart, this.options.url)
24682471

24692472
return this.page
24702473
.waitForFunction(
@@ -2473,12 +2476,12 @@ class Puppeteer extends Helper {
24732476
return currUrl.indexOf(urlPart) > -1
24742477
},
24752478
{ timeout: waitTimeout },
2476-
urlPart,
2479+
expectedUrl,
24772480
)
24782481
.catch(async e => {
2479-
const currUrl = await this._getPageUrl() // Required because the waitForFunction can't return data.
2482+
const currUrl = await this._getPageUrl()
24802483
if (/Waiting failed:/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2481-
throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
2484+
throw new Error(`expected url to include ${expectedUrl}, but found ${currUrl}`)
24822485
} else {
24832486
throw e
24842487
}
@@ -2490,30 +2493,50 @@ class Puppeteer extends Helper {
24902493
*/
24912494
async waitUrlEquals(urlPart, sec = null) {
24922495
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
2493-
2494-
const baseUrl = this.options.url
2495-
let expectedUrl = urlPart
2496-
if (urlPart.indexOf('http') < 0) {
2497-
expectedUrl = baseUrl + urlPart
2498-
}
2496+
const expectedUrl = resolveUrl(urlPart, this.options.url)
24992497

25002498
return this.page
25012499
.waitForFunction(
25022500
url => {
25032501
const currUrl = decodeURIComponent(window.location.href)
2504-
return currUrl.indexOf(url) > -1
2502+
return currUrl === url
25052503
},
25062504
{ timeout: waitTimeout },
25072505
expectedUrl,
25082506
)
25092507
.catch(async e => {
25102508
const currUrl = await this._getPageUrl()
25112509
if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2512-
if (!currUrl.includes(expectedUrl)) {
2513-
throw new Error(`expected url to be ${expectedUrl}, but found ${currUrl}`)
2514-
} else {
2515-
throw new Error(`expected url not loaded, error message: ${e.message}`)
2516-
}
2510+
throw new Error(`expected url to be ${expectedUrl}, but found ${currUrl}`)
2511+
} else {
2512+
throw e
2513+
}
2514+
})
2515+
}
2516+
2517+
/**
2518+
* {{> waitCurrentPathEquals }}
2519+
*/
2520+
async waitCurrentPathEquals(path, sec = null) {
2521+
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
2522+
const normalizedPath = normalizePath(path)
2523+
2524+
return this.page
2525+
.waitForFunction(
2526+
expectedPath => {
2527+
const actualPath = window.location.pathname
2528+
const normalizePath = p => (p === '' || p === '/' ? '/' : p.replace(/\/+/g, '/').replace(/\/$/, '') || '/')
2529+
return normalizePath(actualPath) === expectedPath
2530+
},
2531+
{ timeout: waitTimeout },
2532+
normalizedPath,
2533+
)
2534+
.catch(async e => {
2535+
const currUrl = await this._getPageUrl()
2536+
const baseUrl = this.options.url || 'http://localhost'
2537+
const actualPath = new URL(currUrl, baseUrl).pathname
2538+
if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
2539+
throw new Error(`expected path to be ${normalizedPath}, but found ${normalizePath(actualPath)}`)
25172540
} else {
25182541
throw e
25192542
}

lib/helper/WebDriver.js

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,18 @@ import output from '../output.js'
1313
const { debug } = output
1414
import { empty } from '../assert/empty.js'
1515
import { truth } from '../assert/truth.js'
16-
import { xpathLocator, fileExists, decodeUrl, chunkArray, convertCssPropertiesToCamelCase, screenshotOutputFolder, getNormalizedKeyAttributeValue, modifierKeys } from '../utils.js'
16+
import {
17+
xpathLocator,
18+
fileExists,
19+
decodeUrl,
20+
chunkArray,
21+
convertCssPropertiesToCamelCase,
22+
screenshotOutputFolder,
23+
getNormalizedKeyAttributeValue,
24+
modifierKeys,
25+
normalizePath,
26+
resolveUrl,
27+
} from '../utils.js'
1728
import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
1829
import ElementNotFound from './errors/ElementNotFound.js'
1930
import ConnectionRefused from './errors/ConnectionRefused.js'
@@ -1854,7 +1865,7 @@ class WebDriver extends Helper {
18541865
const currentUrl = await this.browser.getUrl()
18551866
const baseUrl = this.options.url || 'http://localhost'
18561867
const actualPath = new URL(currentUrl, baseUrl).pathname
1857-
return equals('url path').assert(path, actualPath)
1868+
return equals('url path').assert(normalizePath(path), normalizePath(actualPath))
18581869
}
18591870

18601871
/**
@@ -1864,7 +1875,7 @@ class WebDriver extends Helper {
18641875
const currentUrl = await this.browser.getUrl()
18651876
const baseUrl = this.options.url || 'http://localhost'
18661877
const actualPath = new URL(currentUrl, baseUrl).pathname
1867-
return equals('url path').negate(path, actualPath)
1878+
return equals('url path').negate(normalizePath(path), normalizePath(actualPath))
18681879
}
18691880

18701881
/**
@@ -2490,22 +2501,23 @@ class WebDriver extends Helper {
24902501
async waitInUrl(urlPart, sec = null) {
24912502
const client = this.browser
24922503
const aSec = sec || this.options.waitForTimeoutInSeconds
2504+
const expectedUrl = resolveUrl(urlPart, this.options.url)
24932505
let currUrl = ''
24942506

24952507
return client
24962508
.waitUntil(
24972509
function () {
24982510
return this.getUrl().then(res => {
24992511
currUrl = decodeUrl(res)
2500-
return currUrl.indexOf(urlPart) > -1
2512+
return currUrl.indexOf(expectedUrl) > -1
25012513
})
25022514
},
25032515
{ timeout: aSec * 1000 },
25042516
)
25052517
.catch(e => {
25062518
e = wrapError(e)
25072519
if (e.message.indexOf('timeout')) {
2508-
throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
2520+
throw new Error(`expected url to include ${expectedUrl}, but found ${currUrl}`)
25092521
}
25102522
throw e
25112523
})
@@ -2516,22 +2528,47 @@ class WebDriver extends Helper {
25162528
*/
25172529
async waitUrlEquals(urlPart, sec = null) {
25182530
const aSec = sec || this.options.waitForTimeoutInSeconds
2519-
const baseUrl = this.options.url
2520-
if (urlPart.indexOf('http') < 0) {
2521-
urlPart = baseUrl + urlPart
2522-
}
2531+
const expectedUrl = resolveUrl(urlPart, this.options.url)
25232532
let currUrl = ''
25242533
return this.browser
25252534
.waitUntil(function () {
25262535
return this.getUrl().then(res => {
25272536
currUrl = decodeUrl(res)
2528-
return currUrl === urlPart
2537+
return currUrl === expectedUrl
25292538
})
25302539
}, aSec * 1000)
25312540
.catch(e => {
25322541
e = wrapError(e)
25332542
if (e.message.indexOf('timeout')) {
2534-
throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
2543+
throw new Error(`expected url to be ${expectedUrl}, but found ${currUrl}`)
2544+
}
2545+
throw e
2546+
})
2547+
}
2548+
2549+
/**
2550+
* {{> waitCurrentPathEquals }}
2551+
*/
2552+
async waitCurrentPathEquals(path, sec = null) {
2553+
const aSec = sec || this.options.waitForTimeoutInSeconds
2554+
const normalizedPath = normalizePath(path)
2555+
const baseUrl = this.options.url || 'http://localhost'
2556+
let actualPath = ''
2557+
2558+
return this.browser
2559+
.waitUntil(
2560+
async () => {
2561+
const currUrl = await this.browser.getUrl()
2562+
const url = new URL(currUrl, baseUrl)
2563+
actualPath = url.pathname
2564+
return normalizePath(actualPath) === normalizedPath
2565+
},
2566+
{ timeout: aSec * 1000 },
2567+
)
2568+
.catch(e => {
2569+
e = wrapError(e)
2570+
if (e.message.indexOf('timeout')) {
2571+
throw new Error(`expected path to be ${normalizedPath}, but found ${normalizePath(actualPath)}`)
25352572
}
25362573
throw e
25372574
})

lib/utils.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,24 @@ export const decodeUrl = function (url) {
150150
return decodeURIComponent(decodeURIComponent(decodeURIComponent(url)))
151151
}
152152

153+
export const normalizePath = function (path) {
154+
if (path === '' || path === '/') return '/'
155+
return path
156+
.replace(/\/+/g, '/')
157+
.replace(/\/$/, '') || '/'
158+
}
159+
160+
export const resolveUrl = function (url, baseUrl) {
161+
if (!url) return url
162+
if (url.indexOf('http') === 0) return url
163+
if (!baseUrl) return url
164+
try {
165+
return new URL(url, baseUrl).href
166+
} catch (e) {
167+
return url
168+
}
169+
}
170+
153171
export const xpathLocator = {
154172
/**
155173
* @param {string} string

0 commit comments

Comments
 (0)