diff --git a/package.json b/package.json index b67dcc81bf4..7ca9fada60f 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "source-map-support": "^0.5.5", "strip-bom": "^2.0.0", "testcafe-browser-tools": "1.6.5", - "testcafe-hammerhead": "14.3.0", + "testcafe-hammerhead": "14.3.1", "testcafe-legacy-api": "3.1.8", "testcafe-reporter-json": "^2.1.0", "testcafe-reporter-list": "^2.1.0", diff --git a/src/client/automation/playback/scroll.js b/src/client/automation/playback/scroll.js index 55ed2b2e432..756f26d08d2 100644 --- a/src/client/automation/playback/scroll.js +++ b/src/client/automation/playback/scroll.js @@ -145,17 +145,17 @@ export default class ScrollAutomation { }; } - static _getChildPoint (parentDimensions, childDimensions, offsetX, offsetY) { + static _getChildPoint (parentDimensions, childDimensions, offsets) { return { x: childDimensions.left - parentDimensions.left + parentDimensions.scroll.left + - childDimensions.border.left + offsetX, + childDimensions.border.left + offsets.x, y: childDimensions.top - parentDimensions.top + parentDimensions.scroll.top + - childDimensions.border.top + offsetY + childDimensions.border.top + offsets.y }; } - _getScrollPosition (parentDimensions, childDimensions, maxScrollMargin, offsetX, offsetY) { - const childPoint = ScrollAutomation._getChildPoint(parentDimensions, childDimensions, offsetX, offsetY); + _getScrollPosition (parentDimensions, childDimensions, offsets, maxScrollMargin) { + const childPoint = ScrollAutomation._getChildPoint(parentDimensions, childDimensions, offsets); const scrollToPoint = this._getScrollToPoint(parentDimensions, childPoint, maxScrollMargin); const scrollToFullView = this._getScrollToFullChildView(parentDimensions, childDimensions, maxScrollMargin); @@ -165,37 +165,35 @@ export default class ScrollAutomation { return { left, top }; } - static _getChildPointAfterScroll (parentDimensions, childDimensions, left, top) { - const x = Math.round(childDimensions.left + parentDimensions.scroll.left - left + childDimensions.width / 2); - const y = Math.round(childDimensions.top + parentDimensions.scroll.top - top + childDimensions.height / 2); + static _getChildPointAfterScroll (parentDimensions, childDimensions, currentScroll, offsets) { + const x = Math.round(childDimensions.left + parentDimensions.scroll.left - currentScroll.left + offsets.x); + const y = Math.round(childDimensions.top + parentDimensions.scroll.top - currentScroll.top + offsets.y); return { x, y }; } - _isChildFullyVisible (parentDimensions, childDimensions, offsetX, offsetY) { - const { x, y } = ScrollAutomation._getChildPointAfterScroll(parentDimensions, childDimensions, parentDimensions.scroll.left, parentDimensions.scroll.top); + _isChildFullyVisible (parentDimensions, childDimensions, offsets) { + const { x, y } = ScrollAutomation._getChildPointAfterScroll(parentDimensions, childDimensions, parentDimensions.scroll, offsets); + const zeroMargin = { left: 0, top: 0 }; - const { left, top } = this._getScrollPosition(parentDimensions, childDimensions, { - left: 0, - top: 0 - }, offsetX, offsetY); + const { left, top } = this._getScrollPosition(parentDimensions, childDimensions, offsets, zeroMargin); return !this._isTargetElementObscuredInPoint(x, y) && left === parentDimensions.scroll.left && top === parentDimensions.scroll.top; } - _scrollToChild (parent, child, { offsetX, offsetY }) { + _scrollToChild (parent, child, offsets) { const parentDimensions = positionUtils.getClientDimensions(parent); const childDimensions = positionUtils.getClientDimensions(child); const windowWidth = styleUtils.getInnerWidth(window); const windowHeight = styleUtils.getInnerHeight(window); let scrollPos = parentDimensions.scroll; - let needScroll = !this._isChildFullyVisible(parentDimensions, childDimensions, offsetX, offsetY); + let needScroll = !this._isChildFullyVisible(parentDimensions, childDimensions, offsets); while (needScroll) { - scrollPos = this._getScrollPosition(parentDimensions, childDimensions, this.maxScrollMargin, offsetX, offsetY); + scrollPos = this._getScrollPosition(parentDimensions, childDimensions, offsets, this.maxScrollMargin); - const { x, y } = ScrollAutomation._getChildPointAfterScroll(parentDimensions, childDimensions, scrollPos.left, scrollPos.top); + const { x, y } = ScrollAutomation._getChildPointAfterScroll(parentDimensions, childDimensions, scrollPos, offsets); const isTargetObscured = this._isTargetElementObscuredInPoint(x, y); this.maxScrollMargin.left += SCROLL_MARGIN_INCREASE_STEP; @@ -235,10 +233,7 @@ export default class ScrollAutomation { const scrollParentsPromise = promiseUtils.times(parents.length, i => { return this - ._scrollToChild(parents[i], currentChild, { - offsetX: currentOffsetX, - offsetY: currentOffsetY - }) + ._scrollToChild(parents[i], currentChild, { x: currentOffsetX, y: currentOffsetY }) .then(() => { childDimensions = positionUtils.getClientDimensions(currentChild); parentDimensions = positionUtils.getClientDimensions(parents[i]); diff --git a/src/client/test-run/index.js.mustache b/src/client/test-run/index.js.mustache index c3ccd082d24..ed4eea4e202 100644 --- a/src/client/test-run/index.js.mustache +++ b/src/client/test-run/index.js.mustache @@ -4,6 +4,10 @@ var origin = location.origin; + // NOTE: location.origin doesn't exist in IE11 on Windows 10.10240 LTSB + if (!origin) + origin = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : ''); + var testRunId = {{{testRunId}}}; var browserId = {{{browserId}}}; var selectorTimeout = {{{selectorTimeout}}}; diff --git a/src/errors/render-forbidden-chars-list.js b/src/errors/render-forbidden-chars-list.js index a4619f4c7ec..00cd701cb43 100644 --- a/src/errors/render-forbidden-chars-list.js +++ b/src/errors/render-forbidden-chars-list.js @@ -1,3 +1,3 @@ export default function (forbiddenCharsList) { - return forbiddenCharsList.map(charInfo => `\t"${charInfo.char}" at index ${charInfo.index}\n`).join(''); + return forbiddenCharsList.map(charInfo => `\t"${charInfo.chars}" at index ${charInfo.index}\n`).join(''); } diff --git a/src/utils/check-file-path.js b/src/utils/check-file-path.js index 420973f31b1..d58495b4410 100644 --- a/src/utils/check-file-path.js +++ b/src/utils/check-file-path.js @@ -3,31 +3,32 @@ import { win as isWin } from 'os-family'; import sanitizeFilename from 'sanitize-filename'; +const SAFE_CHAR = '_'; +const ALLOWED_CHARS_LIST = [path.win32.sep, path.posix.sep, '.', '..']; + + function correctForbiddenCharsList (forbiddenCharsList, filePath) { const isWinAbsolutePath = isWin && path.isAbsolute(filePath); - const hasDriveSeparatorInList = forbiddenCharsList.length && forbiddenCharsList[0].char === ':' && forbiddenCharsList[0].index === 1; + const hasDriveSeparatorInList = forbiddenCharsList.length && forbiddenCharsList[0].chars === ':' && forbiddenCharsList[0].index === 1; if (isWinAbsolutePath && hasDriveSeparatorInList) forbiddenCharsList.shift(); } -function addForbiddenCharToList (forbiddenCharsList, forbiddenCharInfo) { - const { char } = forbiddenCharInfo; +function addForbiddenCharsToList (forbiddenCharsList, forbiddenCharsInfo) { + const { chars } = forbiddenCharsInfo; - if (char === path.win32.sep || char === path.posix.sep) - return ''; + if (!ALLOWED_CHARS_LIST.includes(chars)) + forbiddenCharsList.push(forbiddenCharsInfo); - forbiddenCharsList.push(forbiddenCharInfo); - - return ''; + return SAFE_CHAR.repeat(chars.length); } - export default function (filePath) { const forbiddenCharsList = []; sanitizeFilename(filePath, { - replacement: (char, index) => addForbiddenCharToList(forbiddenCharsList, { char, index }) + replacement: (chars, index) => addForbiddenCharsToList(forbiddenCharsList, { chars, index }) }); correctForbiddenCharsList(forbiddenCharsList, filePath); diff --git a/test/client/fixtures/automation/regression-test.js b/test/client/fixtures/automation/regression-test.js index a41412c6db4..106466d47f8 100644 --- a/test/client/fixtures/automation/regression-test.js +++ b/test/client/fixtures/automation/regression-test.js @@ -339,33 +339,31 @@ $(document).ready(function () { }); }); - asyncTest('B237672 - TesCafe throw exception "Access is denied" after trying to get content of iframe in IE browsers', function () { - let clicked = false; + asyncTest('B237672 - TesCafe should not throw an exception "Access is denied" on accessing to a content of the cross-domain iframe', function () { + let result = false; const $iframe = $('') .width(500) .height(500) .attr('src', 'http://www.cross.domain.com') - .addClass(TEST_ELEMENT_CLASS) - .click(function () { - clicked = true; - }); + .addClass(TEST_ELEMENT_CLASS); window.QUnitGlobals.waitForIframe($iframe[0]).then(function () { try { - //NOTE: for not ie - const iframeBody = $iframe[0].contentWindow.document; + const iframeDocument = $iframe[0].contentWindow.document; - nativeMethods.addEventListener.call(iframeBody, 'click', function () { - clicked = true; + nativeMethods.addEventListener.call(iframeDocument, 'click', function () { + throw new Error('Click handler on an iframe should not be called'); }); + + result = true; } catch (e) { - // do nothing + result = false; } runClickAutomation($iframe[0], {}, function () { - ok(clicked, 'click was raised'); + ok(result); startNext(); }); }); diff --git a/test/functional/config.js b/test/functional/config.js index 18344629f2b..a92d4584bce 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -73,7 +73,7 @@ testingEnvironments[testingEnvironmentNames.mobileBrowsers] = { { realMobile: true, os: 'android', - osVersion: '7.1', + osVersion: '8.0', device: 'Google Pixel', name: 'Android', alias: 'android' @@ -248,8 +248,8 @@ module.exports = { testCafe: { hostname: hostname, - port1: 2000, - port2: 2001 + port1: 9000, + port2: 9001 }, site: { @@ -264,7 +264,7 @@ module.exports = { } }, - browserstackConnectorServicePort: 4000, + browserstackConnectorServicePort: 9200, browsers: [] }; diff --git a/test/functional/fixtures/regression/gh-1057/pages/hiddenByFixedParent.html b/test/functional/fixtures/regression/gh-1057/pages/hiddenByFixedParent.html index c148f5cf277..cad9ce838f3 100644 --- a/test/functional/fixtures/regression/gh-1057/pages/hiddenByFixedParent.html +++ b/test/functional/fixtures/regression/gh-1057/pages/hiddenByFixedParent.html @@ -13,8 +13,8 @@ position: absolute; left: 500px; top: 500px; - width: 20px; - height: 20px; + width: 200px; + height: 200px; background-color: red; } @@ -22,8 +22,8 @@ position: absolute; left: 2500px; top: 2500px; - width: 20px; - height: 20px; + width: 200px; + height: 200px; background-color: blue; } diff --git a/test/functional/fixtures/regression/gh-1057/test.js b/test/functional/fixtures/regression/gh-1057/test.js index 297469e13f8..3d9d27d5aa7 100644 --- a/test/functional/fixtures/regression/gh-1057/test.js +++ b/test/functional/fixtures/regression/gh-1057/test.js @@ -6,6 +6,14 @@ describe('[Regression](GH-1057) - hidden by fixed parent', function () { skip: 'iphone,ipad,android' }); }); + + it('The target element should not be under the element with position:fixed after scroll when using custom offsets', function () { + return runTests('testcafe-fixtures/hiddenByFixedParent.js', 'gh-1057 with custom offsets', { + // NOTE: https://github.com/DevExpress/testcafe/issues/1237 + // TODO: Android disabled because of https://github.com/DevExpress/testcafe/issues/1492 + skip: 'iphone,ipad,android' + }); + }); }); describe('[Regression](GH-1057) - hidden by fixed ancestor', function () { diff --git a/test/functional/fixtures/regression/gh-1057/testcafe-fixtures/hiddenByFixedParent.js b/test/functional/fixtures/regression/gh-1057/testcafe-fixtures/hiddenByFixedParent.js index da59badf27a..302bdf06ee6 100644 --- a/test/functional/fixtures/regression/gh-1057/testcafe-fixtures/hiddenByFixedParent.js +++ b/test/functional/fixtures/regression/gh-1057/testcafe-fixtures/hiddenByFixedParent.js @@ -12,3 +12,11 @@ test('gh-1057', async t => { .click('#target1') .expect(targetsClicked()).ok(); }); + +test('gh-1057 with custom offsets', async t => { + // NOTE: scrolling has issues in iOS Simulator https://github.com/DevExpress/testcafe/issues/1237 + await t + .click('#target2', { offsetX: -1, offsetY: -1 }) + .click('#target1', { offsetX: 1, offsetY: 1 }) + .expect(targetsClicked()).ok(); +}); diff --git a/test/functional/fixtures/regression/gh-2067/test.js b/test/functional/fixtures/regression/gh-2067/test.js index 2d018013b19..a23ebca86ba 100644 --- a/test/functional/fixtures/regression/gh-2067/test.js +++ b/test/functional/fixtures/regression/gh-2067/test.js @@ -4,7 +4,7 @@ describe('[Regression](GH-2067) - Radio button navigation by keyboard', function }); it('nonamed - chrome', function () { - return runTests('testcafe-fixtures/index.js', 'nonamed - chrome', { only: ['chrome', 'chrome-osx', 'android'] }); + return runTests('testcafe-fixtures/index.js', 'nonamed - chrome', { only: ['chrome', 'chrome-osx'] }); }); it('nonamed - ie, firefox', function () { diff --git a/test/server/runner-test.js b/test/server/runner-test.js index f6565d778ef..7c9f83fd0c6 100644 --- a/test/server/runner-test.js +++ b/test/server/runner-test.js @@ -225,6 +225,22 @@ describe('Runner', () => { 'screenshots path pattern:\n \t":" at index 7\n'); }); }); + + it('Should allow to use relative paths in the screenshots base path and path patterns', () => { + const storedRunTaskFn = runner._runTask; + + runner._runTask = function () { + runner._runTask = storedRunTaskFn; + + return Promise.resolve({}); + }; + + return runner + .browsers(connection) + .screenshots('..', false, '${BROWSER}/./${TEST}') + .src('test/server/data/test-suites/basic/testfile2.js') + .run(); + }); }); describe('.src()', () => { diff --git a/test/server/test-run-error-formatting-test.js b/test/server/test-run-error-formatting-test.js index fcdfc39c836..d9983e08828 100644 --- a/test/server/test-run-error-formatting-test.js +++ b/test/server/test-run-error-formatting-test.js @@ -330,7 +330,7 @@ describe('Error formatting', () => { }); it('Should format "forbiddenCharactersInScreenshotPathError"', () => { - assertErrorMessage('forbidden-characters-in-screenshot-path-error', new ForbiddenCharactersInScreenshotPathError('/root/bla:bla', [{ char: ':', index: 9 }])); + assertErrorMessage('forbidden-characters-in-screenshot-path-error', new ForbiddenCharactersInScreenshotPathError('/root/bla:bla', [{ chars: ':', index: 9 }])); }); it('Should format "invalidElementScreenshotDimensionsError"', () => {