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: 34 additions & 0 deletions examples/tests/scroll-commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module.exports = {
'AngularJS Website Tests': function(browser) {
browser
.url('https://angularjs.org')
.waitForElementVisible('body')
.pause(2000)

// Test scrolling with different methods

// Scroll to bottom and back to top
.scrollToBottom()
.pause(1000)
.scrollToTop()
.pause(1000)

// Find and scroll to elements using different selectors
.waitForElementPresent('#the-basics')
.scrollIntoView({
element: '#the-basics', // Using ID selector
behavior: 'smooth',
block: 'center'
})
.pause(1000)
.scrollToTop()
.pause(1000)
.scrollIntoView({
element: '//*[contains(text(),"Super-powered")]', // Using XPath with text
behavior: 'smooth',
block: 'center'
})

.end();
}
};
81 changes: 81 additions & 0 deletions lib/api/client-commands/scroll/scrollIntoView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
const BaseCommand = require('../_base-command.js');

/**
* Scrolls an element into view with customizable behavior.
* Works for web applications.
*
* @example
* module.exports = {
* 'scroll element into view': function(browser) {
* browser.scrollIntoView({
* element: '#myElement', // Can be CSS selector or XPath
* behavior: 'smooth',
* block: 'center'
* });
* }
* };
*
* @method scrollIntoView
* @param {Object} options The options for scrolling
* @param {string} options.element The selector (CSS) or XPath of the element to scroll to
* @param {string} [options.behavior='auto'] The scroll behavior ('auto', 'smooth')
* @param {string} [options.block='start'] The vertical alignment ('start', 'center', 'end', 'nearest')
* @param {string} [options.inline='nearest'] The horizontal alignment ('start', 'center', 'end', 'nearest')
* @api protocol.window
*/
module.exports = class ScrollIntoView {
command(options, callback) {
const {
element,
behavior = 'auto',
block = 'start',
inline = 'nearest'
} = options;

// Check if it's an XPath selector
const isXPath = element.startsWith('//') || element.startsWith('(//');

const api = this.api;

if (isXPath) {
api.useXpath();
}

return api
.waitForElementPresent(element)
.moveToElement(element, 0, 0)
.execute(function(selector, scrollOptions, isXPathSelector) {
/* global document, XPathResult */
let targetElement;

if (isXPathSelector) {
const xpathResult = document.evaluate(
selector,
document,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
);
targetElement = xpathResult.singleNodeValue;
} else {
targetElement = document.querySelector(selector);
}

if (targetElement) {
targetElement.scrollIntoView(scrollOptions);

return true;
}

return false;
}, [element, {behavior, block, inline}, isXPath], function(result) {
if (isXPath) {
api.useCss();
}

if (typeof callback === 'function') {
callback.call(this, result);
}
});
}
};
28 changes: 28 additions & 0 deletions lib/api/client-commands/scroll/scrollToBottom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const BaseCommand = require('../_base-command.js');

/**
* Scrolls the page to the bottom. Works for web applications.
*
* @example
* module.exports = {
* 'scroll to bottom': function(browser) {
* browser.scrollToBottom();
* }
* };
*
* @method scrollToBottom
* @syntax .scrollToBottom([callback])
* @param {function} [callback] Optional callback function to be called when the command finishes.
* @api protocol.window
*/
class ScrollToBottom extends BaseCommand {
static get isTraceable() {
return true;
}

performAction(callback) {
return this.executeScriptHandler('executeScript', 'window.scrollTo(0, document.body.scrollHeight)', [], callback);
}
}

module.exports = ScrollToBottom;
28 changes: 28 additions & 0 deletions lib/api/client-commands/scroll/scrollToTop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const BaseCommand = require('../_base-command.js');

/**
* Scrolls the page to the top. Works for web applications.
*
* @example
* module.exports = {
* 'scroll to top': function(browser) {
* browser.scrollToTop();
* }
* };
*
* @method scrollToTop
* @syntax .scrollToTop([callback])
* @param {function} [callback] Optional callback function to be called when the command finishes.
* @api protocol.window
*/
class ScrollToTop extends BaseCommand {
static get isTraceable() {
return true;
}

performAction(callback) {
return this.executeScriptHandler('executeScript', 'window.scrollTo(0, 0)', [], callback);
}
}

module.exports = ScrollToTop;
55 changes: 55 additions & 0 deletions lib/api/client-commands/scroll/scrollUntilText.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const BaseCommand = require('../_base-command.js');

/**
* Scrolls the page until the specified text is visible. Works for web applications.
*
* @example
* module.exports = {
* 'scroll until text is visible': function(browser) {
* browser.scrollUntilText('Welcome to our site');
* }
* };
*
* @method scrollUntilText
* @syntax .scrollUntilText(text, [callback])
* @param {string} text The text to scroll until visible.
* @param {function} [callback] Optional callback function to be called when the command finishes.
* @api protocol.window
*/
class ScrollUntilText extends BaseCommand {
static get isTraceable() {
return true;
}

performAction(callback) {
const script = `
function findText(text) {
const walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
null,
false
);

let node;
while (node = walker.nextNode()) {
if (node.textContent.includes(text)) {
return node.parentElement;
}
}
return null;
}

const element = findText(arguments[0]);
if (element) {
element.scrollIntoView({behavior: "smooth", block: "center"});
return true;
}
return false;
`;

return this.executeScriptHandler('executeScript', script, [this.text], callback);
}
}

module.exports = ScrollUntilText;
85 changes: 85 additions & 0 deletions lib/api/client-commands/window/scrollIntoView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* Scrolls an element into view. Works for both web and native apps.
* Supports multiple selector types: CSS, XPath, ID, aria-label, etc.
*
* @example
* module.exports = {
* 'scroll element into view': function(browser) {
* // Using CSS selector
* browser.scrollIntoView('#myElement');
* // Using XPath
* browser.scrollIntoView('//div[@id="myElement"]', 'xpath');
* // Using aria-label
* browser.scrollIntoView('[aria-label="Learn More"]');
* // Using ID
* browser.scrollIntoView('#myElement');
* }
* };
*
* @method scrollIntoView
* @syntax .scrollIntoView(selector, [callback])
* @syntax .scrollIntoView(using, selector, [callback])
* @param {string} [using] The locator strategy to use. See [W3C Webdriver - locator strategies](https://www.w3.org/TR/webdriver/#locator-strategies)
* @param {string} selector The selector used to locate the element.
* @param {function} [callback] Optional callback function to be called when the command finishes.
* @api protocol.window
*/
module.exports = class ScrollIntoView {
command(using, selector, callback) {
let usingValue = 'css selector';
let selectorValue;
let callbackValue;

// Handle different argument combinations
if (arguments.length === 1) {
// scrollIntoView(selector)
selectorValue = using;
// Auto-detect selector type
if (selectorValue.startsWith('//')) {
usingValue = 'xpath';
} else if (selectorValue.startsWith('#')) {
usingValue = 'css selector';
} else if (selectorValue.startsWith('aria=')) {
usingValue = 'css selector';
selectorValue = `[aria-label="${selectorValue.substring(5)}"]`;
}
} else if (arguments.length === 2) {
if (typeof selector === 'function') {
// scrollIntoView(selector, callback)
selectorValue = using;
callbackValue = selector;
// Auto-detect selector type
if (selectorValue.startsWith('//')) {
usingValue = 'xpath';
} else if (selectorValue.startsWith('#')) {
usingValue = 'css selector';
} else if (selectorValue.startsWith('aria=')) {
usingValue = 'css selector';
selectorValue = `[aria-label="${selectorValue.substring(5)}"]`;
}
} else {
// scrollIntoView(using, selector)
usingValue = using;
selectorValue = selector;
}
} else {
// scrollIntoView(using, selector, callback)
usingValue = using;
selectorValue = selector;
callbackValue = callback;
}

return this.api
.waitForElementPresent(selectorValue, usingValue)
.moveToElement(selectorValue, 0, 0, usingValue)
.execute(
'arguments[0].scrollIntoView({behavior: "smooth", block: "center"});',
[{using: usingValue, selector: selectorValue}],
function(result) {
if (typeof callbackValue === 'function') {
callbackValue.call(this, result);
}
}
);
}
};
53 changes: 53 additions & 0 deletions lib/api/client-commands/window/scrollToBottom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const BaseCommand = require('../_base-command');

/**
* Scrolls the page to the bottom. Works for both web and native apps.
*
* @example
* module.exports = {
* 'scroll to bottom': function(browser) {
* browser.scrollToBottom();
* }
* };
*
* @method scrollToBottom
* @syntax .scrollToBottom([callback])
* @param {function} [callback] Optional callback function to be called when the command finishes.
* @api protocol.window
*/
class ScrollToBottom extends BaseCommand {
static get isTraceable() {
return true;
}

async command(callback) {
const {isMobile} = this.client.settings;

if (isMobile) {
// For native apps, use mobile-specific scrolling
return this.executeScript(`
if (window.ReactNativeWebView) {
// React Native WebView
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'scroll',
direction: 'bottom'
}));
} else if (window.webkit && window.webkit.messageHandlers) {
// iOS WKWebView
window.webkit.messageHandlers.scroll.postMessage({
direction: 'bottom'
});
} else {
// Fallback for other mobile webviews
window.scrollTo(0, document.body.scrollHeight);
}
`, [], callback);
}

// For web browsers
return this.executeScript('window.scrollTo(0, document.body.scrollHeight)', [], callback);

}
}

module.exports = ScrollToBottom;
Loading