/* Copyright 2011, 2012, 2013, 2014, 2015, 2016 Peter Kehl
This file is part of SeLite Commands.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
// Following assignments is purely for JSDoc.
/** @class Selenium*/
Selenium= Selenium;
(
function() {
// This functions has Selenium purely for JSDoc, so that the following members are documented in JSDoc 'Selenium' namespace. Side note: uncommenting the following line (or a similar 'var') disabled JSDoc for this namespace.
//let SeleniumExtra= Selenium;
// For all Selenium actions and locators defined here - i.e. functions with name doXXXX, isXXXXX, getXXXXX
// see their user documentation at ../reference.xml
// @TODO report getXyz() function must return a non-null defined value; otherwise you'll get a confusing error from AccessorResult
// at chrome/content/selenium-core/scripts/selenium-commandhandlers.js
// See also https://github.com/SeleniumHQ/selenium/issues/1635.
/** @TODO eliminate? Or, keep, if we use NaN
* @member {function}
**/
Selenium.prototype.doTypeRobust= function doTypeRobust(target, value) {
if( !target || !value ) {
LOG.info( 'typeRobust skipped, since target or value was empty/0/false.' );
}
else {
this.doType( target, value );
}
};
Selenium.prototype.doSelectRobust= function doSelectRobust( selectLocator, optionLocator) {
if( !selectLocator || !optionLocator ) {
LOG.info( 'selectRobust skipped, since selectLocator or optionLocator was empty/0/false.' );
}
else {
this.doSelect( selectLocator, optionLocator );
}
};
Selenium.prototype.doClickRobust= function doClickRobust( locator, valueUnused) {
if( locator==='' ) {
LOG.info( 'clickRobust skipped, since locator was an empty string.' );
}
else {
this.doClick( locator, valueUnused );
}
};
Selenium.prototype.isTimestampDownToMilliseconds= function isTimestampDownToMilliseconds( locator, timestampInMilliseconds ) {
return this.timestampComparesTo( locator, timestampInMilliseconds, 1, true );
};
Selenium.prototype.isTimestampDownToSeconds= function isTimestampDownToSeconds( locator, timestampInMilliseconds ) {
return this.timestampComparesTo( locator, timestampInMilliseconds, 1000, true );
};
Selenium.prototype.isTimestampDownToMinutes= function isTimestampDownToMinutes( locator, timestampInMilliseconds ) {
return this.timestampComparesTo( locator, timestampInMilliseconds, 60000, true );
};
Selenium.prototype.isTimestampDownToPrecision= function isTimestampDownToPrecision( locator, timestampDetails ) {
return this.timestampComparesTo( locator, timestampDetails.timestamp,
timestampDetails.precision, timestampDetails.validatePrecision, timestampDetails.timezone );
};
// The following functions don't have entries in reference.xml, because when they had they didn't show up in Selenium IDE. Selenium IDE overrode them and it showed auto-generated entries based on isTimestampDownToXXX().
Selenium.prototype.doWaitForTimestampDownToMilliseconds=
Selenium.prototype.doWaitForTimestampDownToSeconds=
Selenium.prototype.doWaitForTimestampDownToMinutes=
Selenium.prototype.doWaitForTimestampDownToPrecision=
function doWaitForTimestampDownToXXX( target, value ) {
throw new Error( "Do not use. See http://selite.github.io/ExtraCommands." );
};
/** This will be SeLiteSettings.Module instance for config module extensions.selite-settings.common. I can retrieve it here, but I can't access its field maxTimeDifference here, because that field is only added on-the-fly in callBack part of Command's SeLiteExtensionSequencerManifest.js, which is only after this file is loaded (as a Core extension) by ExtensionSequencer.
* */
var commonSettings= SeLiteSettings.loadFromJavascript( 'extensions.selite-settings.common' );
/** Shorthand function to value of extensions.selite-settings.common field maxTimeDifference. Not in Selenese scope. */
function maxTimeDifference() {
return commonSettings.getField( 'maxTimeDifference' ).getDownToFolder().entry;
}
/** Internal function, used to compare a displayed human-readable timestamp to a numeric timestamp,
* allowing for difference of maxTimeDifference() (milllisec) and this.defaultTimeout (ms) and 1x display time unit (displayPrecisionInSeconds).
I don't use prefix 'do' or 'get' in the name of this function
because it's not intended to be run as Selenium command/getter.
* @param string locator Selenium locator of the element that contains the displayed human-readable (and parsable) time stamp
* @param number timestampInMilliseconds Expected timestamp, number of milliseconds since Epoch
* @param number displayPrecisionInMilliseconds Smallest displayed time unit, in milliseconds
* @param bool validatePrecision
* @TODO Use parameter timezone. Allow both short and long names? Make it daylightsaving-friendly, so that the test can run when daylightsaving changes - don't cache the time shift.
* This doesn't use timezone support in Date.parse(), because that only understands GMT, Z and US time zone abbreviations
* - see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse and try
* Date.parse( "Fri, 11 Oct 2013 05:55:00 AEST" ) - it evaluates to NaN.
* evaluate in a .js file or via Firebug console, not via 'javascript:' url: new Intl.DateTimeFormat("en-GB", {timeZone:"AEDT", timeZoneName:'short'}).format( new Date())
* See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset and
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat/supportedLocalesOf
*
* This is on hold, pending https://bugzilla.mozilla.org/show_bug.cgi?id=837961. In Firefox 47.0a1 timezones seem to work one way only:
* new Intl.DateTimeFormat('en-AU', {timeZoneName: 'short'}).format( new Date() ) -> "12/02/2016, AEDT"
* new Intl.DateTimeFormat("en-AU", {timeZone:"AEDT", timeZoneName:'short'}).format( new Date()) -> RangeError: invalid time zone
*
* new Intl.DateTimeFormat('en-AU', {timeZoneName: 'long'}).format( new Date() ) -> "12/02/2016, Australian Eastern Daylight Time"
* new Intl.DateTimeFormat("en-AU", {timeZone:"Australian Eastern Daylight Time", timeZoneName:'long'}).format( new Date()) -> RangeError: invalid time zone
**/
Selenium.prototype.timestampComparesTo= function timestampComparesTo( locator, timestampInMilliseconds, displayPrecisionInMilliseconds, validatePrecision, timezoneTODO ) {
var element= this.browserbot.findElement(locator);
var displayedTimeString= element.value!==undefined
? element.value
: element.textContent;
var displayedTime= Date.parse( displayedTimeString );
var maxDifference= maxTimeDifference()+ Number(this.defaultTimeout)+ displayPrecisionInMilliseconds;
LOG.debug( 'timestampInMilliseconds: ' +timestampInMilliseconds+ '; DisplayedTimeString: ' +displayedTimeString+ ' is timestamp '
+displayedTime+ ' ms; Calculated max allowed difference: ' +maxDifference+ ' ms.' );
// Following works because timezone shifts are multiplies of 30min.
if( validatePrecision && displayedTime%displayPrecisionInMilliseconds>0 ) {
var msg= 'Timestamp precision validation failed. The displayed timestamp has value of precision lower than the given precision ' +displayPrecisionInMilliseconds+ 'ms. If this worked in past, then the application most likely changed. Change the precision.';
LOG.error( msg );
throw new Error(msg);
}
return Math.abs( timestampInMilliseconds-displayedTime) <= maxDifference;
};
/** Anonymous object (serving as an associative array) {
* string timestampName: anonymous object {
* precision: number, the smallest unit of time displayed on the screen for respective timestamp elements
* nextDistinctTimestamp: number, a nearest future timestamp (in milliseconds)
* (that is, a value returned by Date.now() at that moment) when this timestampName
* can have a new distinct timestamp, which can be distinguished from the last one (and any older ones) using the given precision
* }
* where timestampName is a label/name, usually of a timestamp element or field (DB column),
* or of a whole fieldset (DB table) if it has only one timestamp field (column).
**/
Selenium.prototype.distinctTimestamps= {};
/**I don't use prefix 'do' in the name of this function because it's not intended to be run as Selenium command.
Use to record the moment when you inserted/updated a record of given type, and you want to
* compare that record's timestamp (whether soon or later) as formatted on the webpage (using given precision).
* <br/><br/>Warning: This keeps a count only of timestamps notes since you started Selenium IDE. If you re-started it soon
* after the previous run(s) which could record timestamps, make sure you wait for a sufficient period to get distinct new timestamps.
* @param string timestampName Type/use case group of the record that you're upgrading/inserting. Records that can be compared
* between each other should have same timestampName. Then this assures that they get timestamps that show up as distinct.
* Records with different timestampName can get same timestamps, because they are not supposed to be compared to each other.
* @param int timestampPrecision, the precision (lowest unit) of the timestamp, in milliseconds
**/
Selenium.prototype.noteTimestamp= function noteTimestamp( timestampName, timestampPrecision ) {
timestampPrecision= Number(timestampPrecision);
var nextDistinctTimestamp= Date.now()+ maxTimeDifference() +timestampPrecision;
LOG.debug( 'noteTimestamp: timestampName=' +timestampName+ ', precision=' +timestampPrecision+ ', nextDistinctTimestamp=' +nextDistinctTimestamp);
this.distinctTimestamps[timestampName]= {
precision: timestampPrecision,
nextDistinctTimestamp: nextDistinctTimestamp
};
};
/** This and similar functions have name starting with 'doSleepUntil'. That way when you type 'waitForDistinctTimestamp' in Selenium IDE,
* it doesn't auto-suggest '...AndWait' alternatives, which we don't want and which would confuse user. If the function name
* was any doXyz that doesn't start with 'doSleepUntil', Selenium IDE would auto-suggest '..AndWait' alternative, which don't make sense.
* */
Selenium.prototype.doSleepUntilTimestampDistinctDownToMilliseconds= function doSleepUntilTimestampDistinctDownToMilliseconds( timestampName, precisionInMilliseconds ) {
precisionInMilliseconds= precisionInMilliseconds || 1;
return this.waitForDistinctTimestamp( timestampName, precisionInMilliseconds );
};
Selenium.prototype.doSleepUntilTimestampDistinctDownToSeconds= function doSleepUntilTimestampDistinctDownToSeconds( timestampName, precisionInSeconds ) {
precisionInSeconds= precisionInSeconds || 1;
return this.waitForDistinctTimestamp( timestampName, precisionInSeconds*1000 );
};
Selenium.prototype.doSleepUntilTimestampDistinctDownToMinutes= function doWaitTimestampDistinctDownToMinutes( timestampName, precisionInMinutes ) {
precisionInMinutes= precisionInMinutes || 1;
return this.waitForDistinctTimestamp( timestampName, precisionInMinutes*60000 );
};
/**I don't use prefix 'do' in the name of this function
because it's not intended to be run as Selenium command.
@param string timestampName label/name, usually of a timestamp element or field, for which you want to get a distinct timestamp.
* @param int timestampPrecision, the precision (lowest unit) of the timestamp, in milliseconds.
* @return true if it's safe to create a new timestamp for this type of record, and the timestamp
* will be distinguishable from the previous one.
**/
Selenium.prototype.waitForDistinctTimestamp= function waitForDistinctTimestamp( timestampName, precisionInMilliseconds ) {
if( !(timestampName in this.distinctTimestamps) ) {
LOG.debug( 'waitForDistinctTimestampXXX: No previous timestamp for timestamp name ' +timestampName );
this.noteTimestamp( timestampName, precisionInMilliseconds );
return;
}
if( this.distinctTimestamps[timestampName].precision!==precisionInMilliseconds ) {
var error= "You've called waitForDistinctTimestampXXX for timestampName='" +timestampName+
"', precisionInMilliseconds=" +precisionInMilliseconds+ "ms. But the previous timestamp for this timestampName was recorded with different precision: "+
this.distinctTimestamps[timestampName].precision+ "ms. Please use the same precision for the same timestamp. If you've changed it, please restart Selenium IDE.";
LOG.error( error );
throw new Error( error );
}
var timestampBecomesDistinct= this.distinctTimestamps[timestampName].nextDistinctTimestamp; // in milliseconds
var timeOutFromNow= timestampBecomesDistinct-Date.now();
if( timeOutFromNow<=0 ) {
LOG.debug( 'waitForDistinctTimestampXXX for timestamp ' +timestampName+ ': No need to wait. A distinct timestamp became available ' +(-1*timeOutFromNow)+ ' milliseconds ago.' );
this.noteTimestamp( timestampName, precisionInMilliseconds );
return;
}
LOG.debug( 'waitForDistinctTimestampXXX: waiting for next ' +timeOutFromNow+ ' milliseconds. Now: ' +Date.now()+ ', timestampBecomesDistinct: ' +timestampBecomesDistinct );
var self= this;
return Selenium.decorateFunctionWithTimeout(
function check() {
// Somewhere here Firefox 23.0.1 Browser Console reports a false positive bug: 'anonymous function does not always return a value'. Ingore that.
if( Date.now()>=timestampBecomesDistinct ) {
LOG.debug( 'waitForDistinctTimestampXXX for timestamp ' +timestampName+ ' reached the time correctly. It saves a new timestamp and returns true.');
self.noteTimestamp( timestampName, precisionInMilliseconds );
return true;
}
return false;
},
timeOutFromNow+500/* a buffer - otherwise Selenium reports a timeout */,
function callBack() {
LOG.error( 'waitForDistinctTimestampXXX for timestamp ' +timestampName+ ' timed out. This should not happen. It notes a new timestamp.');
self.noteTimestamp( timestampName, precisionInMilliseconds );
}
);
};
Selenium.prototype.doIndexBy= function doIndexBy( columnOrDetails, sourceVariableName ) {
var indexBy= columnOrDetails;
var resultVariableName= sourceVariableName;
var valuesUnique;
if( typeof columnOrDetails==='object' ) {
indexBy= columnOrDetails.indexBy;
valuesUnique= columnOrDetails.valuesUnique;
if( columnOrDetails.store===undefined ) {
resultVariableName= columnOrDetails.store;
}
}
storedVars[resultVariableName]= SeLiteMisc.collectByColumn( storedVars[sourceVariableName], [indexBy], valuesUnique );
};
// I don't use prefix 'get' or 'do' in the name of this function
// because it's not intended to be run as Selenium getter/command.
Selenium.prototype.randomElement= function randomElement( elementSetXPath ) {
/** This clicks at a random radio button from within a set of radio buttons identified by locator.
* @param string elementSetXPath XPath expression to locate the element(s). Don't include leading 'xpath='.
* It can't be any other Selenium locator. You probably want to match them
* using XPath 'contains' function, e.g. //input[ @type='radio' and contains(@id, 'feedback-') ].
*/
// Can't use global variable 'window' here
var window= this.browserbot.getCurrentWindow();
// There's no getElements(..) function in Selenium API, so I'm using the DOM one
// See https://developer.mozilla.org/en/DOM/document.evaluate
var elementsIterator= window.document.evaluate(
elementSetXPath, window.document, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null );
var elements= [];
var element= null;
while( (element=elementsIterator.iterateNext()) ) {
elements.push( element );
}
if( !elements.length ) {
LOG.error( 'getRandomElement(): There are no elements matching XPath: ' +elementSetXPath );
throw new Error();
}
var elementIndex= Math.round( Math.random()*(elements.length-1) );
return elements[elementIndex];
};
Selenium.prototype.doClickRandom= function doClickRandom( radiosXPath, store ) {
var radio= this.randomElement( radiosXPath );
this.browserbot.clickElement( radio );
if( store ) {
SeLiteMisc.setFields( storedVars, store, radio.value );
}
};
/** This returns a random option from within <select>...</select> identified by a given locator.
I don't use prefix 'do' or 'get' in the name of this function because it's not intended to be run as Selenium command/getter.
* @param string selectLocator Locator of the <select>...</select>. It has to be an XPath-based locator, without 'xpath=' at the front.
* @param {Object<string,boolean>} [params={}] Optional, an object in form {
* excludeFirst: true, // Whether to exclude the first option
* excludeLast: true, // Whether to exclude the last option
* }
* @return DOM Element of a random <option>...</option> from within the select
*/
Selenium.prototype.randomOption= function randomOption( selectLocator, params={} ) {
var select= this.browserbot.findElement(selectLocator);
var options= select.getElementsByTagName('option');
var excludeFirst= params && params.excludeFirst;
var excludeLast= params && params.excludeLast;
var firstIndex= excludeFirst
? 1
: 0;
var randomRange= options.length-1 -firstIndex -(
excludeLast
? 1
: 0 );
var optionIndex= firstIndex+ Math.round( Math.random()*randomRange );
var option= options[optionIndex];
return option;
};
Selenium.prototype.doSelectRandom= function doSelectRandom( selectLocator, paramsOrStore={} ) {
if( typeof paramsOrStore =='string' ) {
paramsOrStore= {store: paramsOrStore};
}
var option= this.randomOption( selectLocator, paramsOrStore );
// This didn't work: this.browserbot.selectOption( select, option );
this.doSelect( selectLocator, /*optionLocator:*/'value=' +option.value );
if( paramsOrStore && paramsOrStore.store ) {
//storedVars[paramsOrStore.store]= option.value;
SeLiteMisc.setFields( storedVars, paramsOrStore.store, option.value );
}
};
Selenium.prototype.randomFirstNames= [
'Alice', 'Betty', 'Charlie', 'Dan', 'Erwin', 'Frank', 'Geraldine', 'Hugo', 'Ismael', 'Julie', 'Karl', 'Lucy', 'Marc',
'Nathan', 'Oliver', 'Susie', 'Tatiana', 'Ursula'
];
Selenium.prototype.randomSurnames= ['Brown', 'Blue', 'Cyan', 'Emerald', 'Green', 'Violet', 'Marble', 'Pink', 'Red', 'Ruby', 'Sunshine', 'White'];
Selenium.prototype.randomThirdNameMinLength= 3;
// Following is also applied to (an optional) random part of email domain, the part after a dash -
Selenium.prototype.randomThirdNameMaxLength= 8;
Selenium.prototype.randomWords= ['amazing', 'cat', 'excellent', 'elephant', 'good', 'frog', 'hamster', 'horse', 'lion', 'mouse', 'happy',
'healthy', 'pretty', 'superb', 'tomcat' ];
// htmlTags must contan an empty string
Selenium.prototype.htmlTags= [ '', 'b', 'i', 'u', 'strike', 'sub', 'sup'];
// Don't enter a dot. Include at least one two-letter domain, so that it gets included in randomTopDomainsShort.
Selenium.prototype.randomTopDomains= ['es', 'it', 'com', 'com.au', 'co.nz', 'co.uk'];
/**I don't use prefix 'do' or 'get' in the name of this function
because it's not intended to be run as Selenium command/getter.
*/
Selenium.prototype.randomTopDomainsShort= [];
for( var i=0; i<Selenium.prototype.randomTopDomains.length; i++ ) {
if( Selenium.prototype.randomTopDomains[i].length==2 ) {
Selenium.prototype.randomTopDomainsShort.push( Selenium.prototype.randomTopDomains[i] );
}
}
/**I don't use prefix 'do' or 'get' in the name of this function
because it's not intended to be run as Selenium command/getter.
Return a random text, restricted by params, and fit for an input element identified by locator. It always returns at least 1 character.
* @parameter {object} params object, see parameter paramsOrStore of function doTypeRandom() in ../reference.xml - except that here it has to be an object, it can't be a string.
* @parameter {object} extraParams Currently only used with type 'email', to generate an email address based on
* a given name (first/last/full). Then pass an object {
* baseOnName: string human-name; the email address part left of '@' will be based on this string
* }
* @parameter {string} [locator] Locator of the text input. Used to get max. length of the generated input.
* @return string as speficied in doTypeRandom()
*/
Selenium.prototype.randomText= function randomText( params={}, extraParams, locator ) {
var type= params.type || null;
if( type &&
(typeof type!=='string' || ['email', 'name', 'word', 'number', 'text', 'html', 'password', 'ugly'].indexOf(type)<0)
) {
LOG.error( "randomText(): params.type='" +type+ "' is not recognised." );
throw new Error();
}
!( params.characters && params.type ) || SeLiteMisc.fail("Can't use both parameter subfields 'characters' and 'type'.");
var elementMaxLength= locator
? parseInt( this.browserbot.findElement(locator).getAttribute('maxlength') )
: undefined;
var minLength= params.minLength || 1;
minLength= Math.max( minLength, 1 );
if( type==='email' ) {
minLength= Math.max( minLength, 7 );
}
var maxLength= params.maxLength || elementMaxLength || 255;
maxLength= Math.max( minLength, maxLength );
/** @type {string} */var charRange= ''; // We'll use ASCII characters from within this range
/** @type {RegExp} */var acceptableChars;
if( params.characters ) {
if( Array.isArray(params.characters) ) {
params.characters= params.characters.join('');
}
if( typeof params.characters==='string' ) {
!( params.characters.length>=2 && params.characters.startsWith('/') && params.characters.endsWith('/') ) || SeLiteMisc.fail( "It looks that parameter subfield 'characters' is a string representing a regular expression. Please pass a regular expression itself (i.e. not enclosed in apostrophes or quotation marks). Alternatively pass a string or an array of acceptable characters." );
params.characters= new RegExp( '[' +params.characters.replace('\\', '\\\\').replace('[','\\]') + ']' );
}
SeLiteMisc.ensureInstance( params.characters, RegExp, "params.characters (if specified and other than a string or an array; not a string that is a regular expression)" );
acceptableChars= SeLiteMisc.acceptableCharacters( params.characters );
}
else {
if( !type || type==='email' ) {
charRange+= 'a-zA-Z'; // Email characters @ and . will be added when generating an email address
}
if( !type || type==='number' ) {
charRange+= '0-9';
}
if( type==='name' || type==='word' ) {
charRange+= 'a-z'; // Only used to fill-up after 'nice' first & last name
}
if( type==='text' || type==='html' ) {
charRange+= ' a-z';
}
if( type==='password' ) {
charRange+= 'a-z'; // There's more added below
}
if( !type ) {
charRange+= ' _-';
}
if( type==='ugly' ) {
charRange= "'\"./<>();";
}
acceptableChars= SeLiteMisc.acceptableCharacters( new RegExp( '['+charRange+']' ) );
}
var result= '';
if( type==='name' ) {
result= SeLiteMisc.randomItem( this.randomFirstNames )+ ' ' +SeLiteMisc.randomItem( this.randomSurnames );
// Append random name-like string, min. randomThirdNameMinLength letters, max. randomThirdNameMaxLength letters,
// plust a leading space, e.g. ' Xu...'
if( result.length < maxLength-this.randomThirdNameMinLength ) {
var thirdNameLength= this.randomThirdNameMinLength + Math.round( Math.random()*Math.min(
this.randomThirdNameMaxLength-this.randomThirdNameMinLength,
maxLength-1- this.randomThirdNameMinLength-result.length
) );
result+= ' ' +SeLiteMisc.randomChar(acceptableChars).toUpperCase() +
SeLiteMisc.randomString( acceptableChars, thirdNameLength-1);
}
result= result.substr( 0, maxLength ); // In case we've overrun it (by first or first+' '+last)
}
else
if( type==='email' ) {
if( extraParams ) {
var name= '';
var baseOnName= extraParams.baseOnName.replace( / /g, '-' );
for( var i=0; i<baseOnName.length; i++ ) {
if( acceptableChars.indexOf(baseOnName[i])>=0 ) {
name+= baseOnName[i];
}
}
if( name.length===0 ) {
name= SeLiteMisc.randomChar( acceptableChars );
}
}
else {
// Generate email in form 'name@domain'. For nice human experience of testers we're generating
// first.last@word.word-and-random-filling.(com|it|...) or
// first@word.word-and-random-filling.(com|it|...) for c.a. 50% of results each.
// If the part left of '@' were much longer, it's difficult to identify the email pattern by a human.
var name= SeLiteMisc.randomItem( this.randomFirstNames ).toLowerCase();
// If there's enough space, then for 50% of such cases, append a dot and a second name/part of it
// Leave at least 9 characters for domain (that covers xy.com.au)
if( name.length<maxLength-8 && Math.random()>0.5 ) {
name+= '.' +SeLiteMisc.randomItem( this.randomSurnames ).toLowerCase();
}
}
name= name.substr(0, maxLength-8 ); // In case we've overrun, leave at least 8 letters for full domain
var topDomains= maxLength- name.length>8
? this.randomTopDomains
: this.randomTopDomainsShort;
var topDomain= SeLiteMisc.randomItem( topDomains );
var maxMidDomainLength= Math.max( maxLength-name.length-topDomain.length-2, 2 );
var midDomain= SeLiteMisc.randomItem( this.randomWords ).toLowerCase();
if( maxMidDomainLength-midDomain.length >=2 ) {
// Let's append a dash followed by 1 or more random alpha letters, e.g. '-xpqr', to midDomain
var midDomainExtraLength= 1+ Math.round( Math.random()* Math.min(
this.randomThirdNameMinLength,
maxMidDomainLength- 2- midDomain.length
) );
midDomain+= '-' +SeLiteMisc.randomString(acceptableChars, midDomainExtraLength ).toLowerCase();
}
midDomain= midDomain.substr(0, maxMidDomainLength );
result= name+ '@'+ midDomain+ '.' +topDomain;
}
else
if( !type || ['word', 'text', 'html', 'password', 'ugly', 'number'].indexOf(type)>=0 || params.characters ) {
if( type==='ugly' ) {
// If possible, try to type all ugly characters at least once. But don't type more than maxLength
minLength= Math.min( maxLength, Math.max(minLength,acceptableChars.length) );
}
if( type==='password' && minLength===1 ) {
minLength= 9;
}
var totalLength= minLength+ Math.round( Math.random()*(maxLength-minLength) );
if( type==='text' || type==='html' ) {
var entries= [];
var lengthOfEntries= 0;
while( lengthOfEntries<totalLength ) {
var entry= this.randomWords[Math.round( Math.random()*(this.randomWords.length-1) )];
if( type==='html' ) {
var htmlTags= Selenium.prototype.htmlTags;
if( params.htmlTags ) {
htmlTags= params.htmlTags;
if( htmlTags.indexOf('')<0 ) {
htmlTags.push( '' ); // to generate plain text with no tag
}
}
var tag= htmlTags[Math.round( Math.random()*(htmlTags.length-1) )];
if( tag ) {
entry= '<' +tag+ '>' +entry+ '</' +tag+ '>';
}
}
entries.push( entry );
lengthOfEntries+= entry.length+1;
}
if( lengthOfEntries>totalLength ) {
lengthOfEntries-= entries[entries.length-1].length;
entries.pop();
if( lengthOfEntries<minLength ) {
entries.push( SeLiteMisc.randomString(acceptableChars, minLength-lengthOfEntries) );
}
}
result= entries.join(' ');
}
else if( type==='password' ) {
var capitals= SeLiteMisc.acceptableCharacters( new RegExp( '[A-Z]' ) );
while( result.length+4<=minLength ) {
result+= SeLiteMisc.randomChar( acceptableChars );
result+= SeLiteMisc.randomChar( capitals );
result+= SeLiteMisc.randomChar( '0123456789' );
result+= SeLiteMisc.randomChar( '!@#$%^&*()-_' );
}
result+= SeLiteMisc.randomString(acceptableChars, totalLength-result.length );
}
else if( type==='ugly' ) {
// Typing as many unique ugly characters as possible
var numFirstUglies= Math.min( maxLength, acceptableChars.length );
result= acceptableChars.substr(0, numFirstUglies);
// Typing the rest (still, ugly ones)
result+= SeLiteMisc.randomString(acceptableChars, totalLength-numFirstUglies);
}
else
if( type==='number' ) {
if( params.decimal ) {
if( totalLength<3 ) {
totalLength= 3;
assert( totalLength<=maxLength, "Cannot insert a decimal point" );
}
var maxScale= params.scale
? params.scale
: totalLength-2;
var actualScale= 1+Math.round( Math.random()*(maxScale-1) );
}
else {
var actualScale= 0;
}
if( params.min!==undefined || params.max ) {
// Ignore minLength, maxLength, totalLength
var min= params.min || 0;
result= min+ Math.random()*( params.max-min ); // That excludes params.max. Therefore I'll do a rounding transformation in the next step
// Here I'll shift the decimal point maxScale digits to the right; then I round it; then I shift the decimal point back.
// This way the result range will include params.max as its maximum (if not decimal, or if actualScale==maxScale)
var scaleMultiplier= Math.pow( 10, actualScale );
result= ''+ Math.round( result*scaleMultiplier )/scaleMultiplier;
}
else
if( params.decimal ) {
// We generate a number with totalLength digits. Then we replace one by the decimal point
result= SeLiteMisc.randomString(acceptableChars, totalLength);
var decimalPointPosition= totalLength-actualScale-1;
result= result.substring( 0, decimalPointPosition )+ '.' +result.substring(decimalPointPosition+1);
if( result[totalLength-1]=='0' ) {
result= result.substring( 0, result.length-1 )+ '1';
}
}
else {
result= '' +Math.round( Math.random()*Math.floor(params.max) );
}
}
else {
result= SeLiteMisc.randomString(acceptableChars, totalLength);
}
}
else {
throw new Error( "Error in randomText(): type=" +type );
}
if( params.store ) {
SeLiteMisc.setFields( storedVars, params.store, result );
}
return result;
};
Selenium.prototype.doTypeRandom= function doTypeRandom( locator, paramsOrStore={} ) {
if( typeof paramsOrStore ==='string' ) {
paramsOrStore= {store: paramsOrStore};
}
var resultString= this.randomText( paramsOrStore, undefined, locator );
LOG.debug('doTypeRandom() typing: ' +resultString );
this.doType( locator, resultString );
if( paramsOrStore.store ) {
SeLiteMisc.setFields( storedVars, paramsOrStore.store, resultString );
}
};
// @TODO This doesn't work well
Selenium.prototype.doTypeRandomEmail= function doTypeRandomEmail( locator, params={} ) {
var paramsToPass= { type: 'email' };
if( typeof params==='string' ) {
var name= params;
}
else {
if( params.from!==undefined ) {
var fromElement= this.browserbot.findElement( params.from );
var name= fromElement.value!==undefined
? fromElement.value
: fromElement.textContent;
}
else
if( params.name===undefined ) {
var name= params.name;
}
else {
throw new Error( "You must pass the name to use for the email address, or pass an object with 'from' field which is a locator." );
}
SeLiteMisc.objectClone( params, ['minLength', 'maxLength', 'store'], [], paramsToPass );
}
var extraParamsToPass= {
baseOnName: name
};
var resultString= this.randomText( paramsToPass, extraParamsToPass, locator );
LOG.debug('doTypeRandomEmail() typing: ' +resultString );
this.doType( locator, resultString );
};
// @TODO what did I want to do here?
// @TODO similar doClickMapped?
Selenium.prototype.doSelectMapped= function doSelectMapped( locator, params ) {
};
Selenium.prototype.isSelectMapped= function isSelectMapped( locator, params ) {
};
// @TODO use the 2nd parameter - for an (optional) timeout in milliseconds
Selenium.prototype.doSelectTopFrameAnd= function doSelectTopFrameAnd( locatorOrLocators, unused ) {
if( typeof locatorOrLocators==='string' ) {
locatorOrLocators= locatorOrLocators!==''
? [locatorOrLocators]
: [];
}
Array.isArray(locatorOrLocators) || SeLiteMisc.fail( 'locatorOrLocators must be a selector string, or an array (of selector strings)');
var self= this;
return Selenium.decorateFunctionWithTimeout(
function () {
self.doSelectFrame( 'relative=top' );
for( var locator of locatorOrLocators ) {
var wrappedElementOrNull= self.browserbot.findElementOrNull( locator );
if( wrappedElementOrNull!==null ) {
self.doSelectFrame( locator );
continue;
}
return false;
}
return true;
},
this.defaultTimeout
);
};
//--------------------------
// Based on http://thom.org.uk/2006/03/12/disabling-javascript-from-selenium/
var preferencesService= Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefBranch);
/**I don't use prefix 'do' or 'get' in the name of this function
because it's not intended to be run as Selenium command/getter.
*/
Selenium.prototype.setJavascriptPref= function setJavascriptPref( bool ) {
preferencesService.setBoolPref("javascript.enabled", bool);
};
// Beware: this disables Javascript in whole Firefox (for all tabs). The setting
// will stay after you close Selenium.
Selenium.prototype.doDisableJavascript= function doDisableJavascript() {
this.setJavascriptPref(false);
};
Selenium.prototype.doEnableJavascript= function doEnableJavascript() {
this.setJavascriptPref(true);
};
//--------------------------
Selenium.prototype.doEnsureUnderWebRoot= function doEnsureUnderWebRoot( forceReload ) {
this.browserbot.selectWindow( null );
if( !this.browserbot.getCurrentWindow().location.href.startsWith( SeLiteSettings.webRoot() ) || forceReload ) {
return this.doOpen( SeLiteSettings.webRoot() );
}
};
/* @TODO
var originalClick= Selenium.prototype.doClick;
Selenium.prototype.doClick= function doClick( locator ) {
var element= this.browserbot.findElementOrNull( locator );
if( !element || !this.isVisible(locator) ) {
var msg= "Element " +locator+ (!element
? " is not on the page."
: " is on the page, but it is not visible."
);
throw false//assert-like
? new SeleniumError( msg )
: new Error( msg ); // This: 'Unexpected Exception'
}
return originalClick.call( this, locator );
};
*/
Selenium.prototype.doNop= function doNop( locatorUnused, valueUnused ) {};
Selenium.prototype.doNopAndWait= function doNopAndWait( locatorUnused, valueUnused ) {
throw new SeleniumError( "Do not use nopAndWait. Use nop instead." );
};
Selenium.prototype.doLog= function doLog( message, level ) {
message= '' +message;
level= level
? (''+level).toLowerCase()
: 'error';
switch( level ) {
case 'debug': LOG.debug( message ); break;
case 'info': LOG.info( message ); break;
case 'warn': LOG.warn( message ); break;
case 'error': LOG.error( message ); break;
}
};
Selenium.prototype.doLogAndWait= function doLogAndWait( locatorUnused, valueUnused ) {
throw new SeleniumError( "Do not use logAndWait. Use log instead." );
};
/** This allows to access .gBrowser. Other ways failed: window.gBrowser, selenium.browserbot.getCurrentWindow().gBrowser, window.opener.gBrowser.
* */
Selenium.recentWindow= function recentWindow() {
// Based on https://developer.mozilla.org/en-US/Add-ons/Code_snippets/Tabbed_browser#From_a_dialog
return Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator).getMostRecentWindow("navigator:browser");
};
}
)();