Skip to content
Open
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
34 changes: 32 additions & 2 deletions lib/helper/Playwright.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
clearString,
requireWithFallback,
normalizeSpacesInString,
normalizePath,
relativeDir,
} from '../utils.js'
import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
Expand Down Expand Up @@ -2412,7 +2413,7 @@ class Playwright extends Helper {
const currentUrl = await this._getPageUrl()
const baseUrl = this.options.url || 'http://localhost'
const actualPath = new URL(currentUrl, baseUrl).pathname
return equals('url path').assert(path, actualPath)
return equals('url path').assert(normalizePath(path), normalizePath(actualPath))
}

/**
Expand All @@ -2422,7 +2423,7 @@ class Playwright extends Helper {
const currentUrl = await this._getPageUrl()
const baseUrl = this.options.url || 'http://localhost'
const actualPath = new URL(currentUrl, baseUrl).pathname
return equals('url path').negate(path, actualPath)
return equals('url path').negate(normalizePath(path), normalizePath(actualPath))
}

/**
Expand Down Expand Up @@ -3433,6 +3434,35 @@ class Playwright extends Helper {
}
}

/**
* {{> waitCurrentPathEquals }}
*/
async waitCurrentPathEquals(path, sec = null) {
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
const normalizedPath = normalizePath(path)

try {
await this.page.waitForFunction(
expectedPath => {
const actualPath = window.location.pathname
const normalizePath = p => (p === '' || p === '/' ? '/' : p.replace(/\/+/g, '/').replace(/\/$/, '') || '/')
return normalizePath(actualPath) === expectedPath
},
{ timeout: waitTimeout },
normalizedPath,
)
} catch (e) {
const currentUrl = await this._getPageUrl()
const baseUrl = this.options.url || 'http://localhost'
const actualPath = new URL(currentUrl, baseUrl).pathname
if (/Timeout/i.test(e.message)) {
throw new Error(`expected path to be ${normalizedPath}, but found ${normalizePath(actualPath)}`)
} else {
throw e
}
}
}

/**
* {{> waitForText }}
*/
Expand Down
34 changes: 32 additions & 2 deletions lib/helper/Puppeteer.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
isModifierKey,
requireWithFallback,
normalizeSpacesInString,
normalizePath,
} from '../utils.js'
import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
import ElementNotFound from './errors/ElementNotFound.js'
Expand Down Expand Up @@ -1691,7 +1692,7 @@ class Puppeteer extends Helper {
const currentUrl = await this._getPageUrl()
const baseUrl = this.options.url || 'http://localhost'
const actualPath = new URL(currentUrl, baseUrl).pathname
return equals('url path').assert(path, actualPath)
return equals('url path').assert(normalizePath(path), normalizePath(actualPath))
}

/**
Expand All @@ -1701,7 +1702,7 @@ class Puppeteer extends Helper {
const currentUrl = await this._getPageUrl()
const baseUrl = this.options.url || 'http://localhost'
const actualPath = new URL(currentUrl, baseUrl).pathname
return equals('url path').negate(path, actualPath)
return equals('url path').negate(normalizePath(path), normalizePath(actualPath))
}

/**
Expand Down Expand Up @@ -2496,6 +2497,35 @@ class Puppeteer extends Helper {
})
}

/**
* {{> waitCurrentPathEquals }}
*/
async waitCurrentPathEquals(path, sec = null) {
const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
const normalizedPath = normalizePath(path)

return this.page
.waitForFunction(
expectedPath => {
const actualPath = window.location.pathname
const normalizePath = p => (p === '' || p === '/' ? '/' : p.replace(/\/+/g, '/').replace(/\/$/, '') || '/')
return normalizePath(actualPath) === expectedPath
},
{ timeout: waitTimeout },
normalizedPath,
)
.catch(async e => {
const currUrl = await this._getPageUrl()
const baseUrl = this.options.url || 'http://localhost'
const actualPath = new URL(currUrl, baseUrl).pathname
if (/Waiting failed/i.test(e.message) || /failed: timeout/i.test(e.message)) {
throw new Error(`expected path to be ${normalizedPath}, but found ${normalizePath(actualPath)}`)
} else {
throw e
}
})
}

/**
* {{> waitForText }}
*/
Expand Down
44 changes: 41 additions & 3 deletions lib/helper/WebDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,17 @@ import output from '../output.js'
const { debug } = output
import { empty } from '../assert/empty.js'
import { truth } from '../assert/truth.js'
import { xpathLocator, fileExists, decodeUrl, chunkArray, convertCssPropertiesToCamelCase, screenshotOutputFolder, getNormalizedKeyAttributeValue, modifierKeys } from '../utils.js'
import {
xpathLocator,
fileExists,
decodeUrl,
chunkArray,
convertCssPropertiesToCamelCase,
screenshotOutputFolder,
getNormalizedKeyAttributeValue,
modifierKeys,
normalizePath,
} from '../utils.js'
import { isColorProperty, convertColorToRGBA } from '../colorUtils.js'
import ElementNotFound from './errors/ElementNotFound.js'
import ConnectionRefused from './errors/ConnectionRefused.js'
Expand Down Expand Up @@ -1851,7 +1861,7 @@ class WebDriver extends Helper {
const currentUrl = await this.browser.getUrl()
const baseUrl = this.options.url || 'http://localhost'
const actualPath = new URL(currentUrl, baseUrl).pathname
return equals('url path').assert(path, actualPath)
return equals('url path').assert(normalizePath(path), normalizePath(actualPath))
}

/**
Expand All @@ -1861,7 +1871,7 @@ class WebDriver extends Helper {
const currentUrl = await this.browser.getUrl()
const baseUrl = this.options.url || 'http://localhost'
const actualPath = new URL(currentUrl, baseUrl).pathname
return equals('url path').negate(path, actualPath)
return equals('url path').negate(normalizePath(path), normalizePath(actualPath))
}

/**
Expand Down Expand Up @@ -2534,6 +2544,34 @@ class WebDriver extends Helper {
})
}

/**
* {{> waitCurrentPathEquals }}
*/
async waitCurrentPathEquals(path, sec = null) {
const aSec = sec || this.options.waitForTimeoutInSeconds
const normalizedPath = normalizePath(path)
const baseUrl = this.options.url || 'http://localhost'
let actualPath = ''

return this.browser
.waitUntil(
async () => {
const currUrl = await this.browser.getUrl()
const url = new URL(currUrl, baseUrl)
actualPath = url.pathname
return normalizePath(actualPath) === normalizedPath
},
{ timeout: aSec * 1000 },
)
.catch(e => {
e = wrapError(e)
if (e.message.indexOf('timeout')) {
throw new Error(`expected path to be ${normalizedPath}, but found ${normalizePath(actualPath)}`)
}
throw e
})
}

/**
* {{> waitForText }}
*
Expand Down
7 changes: 7 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ export const decodeUrl = function (url) {
return decodeURIComponent(decodeURIComponent(decodeURIComponent(url)))
}

export const normalizePath = function (path) {
if (path === '' || path === '/') return '/'
return path
.replace(/\/+/g, '/')
.replace(/\/$/, '') || '/'
}

export const xpathLocator = {
/**
* @param {string} string
Expand Down
55 changes: 55 additions & 0 deletions test/helper/webapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,39 @@ export function tests() {
await I.seeCurrentPathEquals('/info')
await I.dontSeeCurrentPathEquals('/info#section')
})

it('should normalize trailing slashes in path comparison', async () => {
await I.amOnPage('/info/')
await I.seeCurrentPathEquals('/info')
await I.seeCurrentPathEquals('/info/')

await I.amOnPage('/form/field/')
await I.seeCurrentPathEquals('/form/field')
await I.seeCurrentPathEquals('/form/field/')
})

it('should normalize multiple consecutive slashes in path', async () => {
await I.amOnPage('/form//field')
await I.seeCurrentPathEquals('/form/field')
await I.seeCurrentPathEquals('/form//field')
})

it('should handle root path correctly', async () => {
await I.amOnPage('/')
await I.seeCurrentPathEquals('/')
await I.seeCurrentPathEquals('')
await I.dontSeeCurrentPathEquals('/info')
})

it('should normalize both expected and actual paths', async () => {
await I.amOnPage('/form/field/')
await I.seeCurrentPathEquals('/form/field/')
await I.seeCurrentPathEquals('/form/field')

await I.amOnPage('/form//field//')
await I.seeCurrentPathEquals('/form/field')
await I.seeCurrentPathEquals('/form/field/')
})
})

describe('#waitInUrl, #waitUrlEquals', () => {
Expand All @@ -139,6 +172,28 @@ export function tests() {
})
})

describe('#waitCurrentPathEquals', () => {
it('should wait for path to match (ignoring query strings)', async () => {
await I.amOnPage('/info')
await I.waitCurrentPathEquals('/info')
})

it('should wait timeout with proper error message', async () => {
try {
await I.amOnPage('/info')
await I.waitCurrentPathEquals('/nonexistent', 0.1)
} catch (e) {
assert.include(e.message, 'expected path to be /nonexistent')
}
})

it('should normalize paths when comparing', async () => {
await I.amOnPage('/form/field/')
await I.waitCurrentPathEquals('/form/field')
await I.waitCurrentPathEquals('/form/field/')
})
})

describe('see text : #see', () => {
it('should check text on site', async () => {
await I.amOnPage('/')
Expand Down
Loading