Testing using Selenium IDE, tips and hints

Saturday, Apr 18, 2015

The following are things that we have found useful when creating Selenium IDE scripts in testing CourseSales.com or automating data transfer/interactions with other third party web-based software applications.

If you wish to do some online tutorials for Selenium visit www.guru99.com/selenium-tutorial.html, the tutorials are created by an IBM veteran. The course covers:

  • Selenium Introduction
  • Selenium IDE Tutorial
  • Selenium WebDriver Tutorial
  • Xpath - Advanced Locators
  • Accessing Web Elements
  • TestNG and Maven
  • Advanced Concepts on Selenium WebDriver
  • Selenium Grid
  • Interview Questions & Answers
  • LIVE PROJECTS for Real Time Exercises

Useful Selenium IDE commands

Increment variable ‘Count’ when using ‘goto’ commands and loops to keep track of numbers of iterations. Be sure to define the variable at the count required, then place this after the action or use of the Count value to increment it. Useful when identifying items by list ID eg: //li[${Count}]/div[3]/div[2]/div/div/span/a/span

<tr>
    <td>store</td>
    <td>javascript{storedVars.Count++;}</td>
    <td></td>
</tr>

Adding text to tinymce when only one tinymce is on a page

<tr>
    <td>runScript</td>
    <td>tinyMCE.activeEditor.setContent('${Paragraphs}') </td>
    <td></td>
</tr>

Adding text to tinymce when more than one tinymce is on a page, using the id of the textarea

<tr>
    <td>runScript</td>
    <td>tinyMCE.get('id_introeditor').setContent('${Paragraphs}')</td>
    <td></td>
</tr>

Modify existing values for matching

To do pattern matching on prices you need to format the price value correctly. CourseSales.com formats the prices to include a comma between the thousand figures. random price values is one way to confirm that a price has appeared on a public or admin pages correctly. The following formats a value to a CourseSales.com formatted amount. eg. OurPrice = 123456, formatted this is 123,465

<tr>
    <td>storeEval</td>
    <td>storedVars['OurPrice'].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,");</td>
    <td>OurPriceFormated</td>
</tr>

Using while commands to iterate actions until a specific value is reached (use in conjunction with Increment variable ‘Count’ above.)

<tr>
    <td>while</td>
    <td>"${StepCount}" <= "9"</td>
    <td></td>
</tr>

Identifying elements containing specific text.

This allows you to then modify actions depending on if the variable ‘QualificationExists’ is true or not.

<tr>
    <td>storeElementPresent</td>
    <td>//h1[(.='Qualification details')]</td>
    <td>QualificationExists</td>
</tr>

Extracting values

Extract domain names from URLs

<tr>
    <td>storeEval</td>
    <td>re=/(https:\/\/[\w .]+)\/rest/; re.exec(storedVars["CSURL"])[1]</td>
    <td>CSURL</td>
</tr>

Extract a number from a string eg. 23 from “Displaying 3 of 23”, which allows you to add and subtract from that number (to use with while and Increment variable ‘Count’).

<tr>
    <td>storeEval</td>
    <td>re=/ of ([0-9]+)/; re.exec(storedVars["PageNumberFooter"])[1]</td>
    <td>PageNumber</td>
</tr>

Another way to do the same thing that works better in some situations. This extracts a numerical postcode from a location string “Ashgrove, 4060”:

<tr>
    <td>storeEval</td>
    <td>RegExp(', ([0-9]+)').exec('${Location}')[1]</td>
    <td>PostCode</td>
</tr>

Extracting the Locality from the same string as above.

<tr>
    <td>storeEval</td>
    <td>re=/\(([\w\s,]+), [0-9]+/; re.exec(storedVars["Location"])[1]</td>
    <td>Locality</td>
</tr>

Extracting from a delimited list, eg. getting the first value (date) from a string like 07-06-1973

<tr>
    <td>storeEval</td>
    <td>"${DateOfBirth}".split("-")[0]</td>
    <td>DateOfBirthYear</td>
</tr>

Extracting the count of a particular character sequence from a string (read more here), this might be useful if you need to know how many items need to be parsed from a comma (or otherwise) separated list, eg. when the string Name has the value Option1>Option2>Option3 the returned value for variable OptionNumber is “2”:

<tr>
    <td>storeEval</td>
    <td>("${Name}".match(/>/g) || []).length</td>
    <td>OptionNumber</td>
</tr>

Replacing values

When you want to replace “fox” with “hound” in the variable “Section1”

<tr>
    <td>store</td>
    <td>javascript{re= new RegExp("fox",'gi'); storedVars.Section1.replace(re, "hound");}</td>
    <td>Section1</td>
</tr>

Slow responding pages or slow Internet connections

Use the following commands:

clickAndWait instead of click
pause
setTimeout
waitForPageToLoad
waitForElementPresent
Increase the default timeout in Selenium IDE options

If the page is extremely slow or is only processed after a specific time you could poll it with a loop and check for the presence of an item. eg. web-based cron in Moodle. store 0 CourseDatesToProcess while ‘${CourseDatesToProcess}’ < ‘1’ pause 50000 open ${MoodleBase}/admin/cron.php storeBodyText BodyText storeEval re=/Number of CourseSales.com dates to process: ([0-9]+)/; re.exec(storedVars[“BodyText”])[1] CourseDatesToProcess endWhile

For sites and pages that take a long time to load place this just after the click or open, and add a ‘waitForElementPresent’ command afterwards or instead of.

<tr>
    <td>waitForPageToLoad</td>
    <td>6000</td>
    <td></td>
</tr>

Some pages need a file uploaded before they can be submitted. This can’t be done using Selenium and therefore you need to do it manually. Here is an alert to the tester to upload the file to continue the test, it will not progress until the upload is completed.

<tr>
    <td>storeEval</td>
    <td>alert("Upload a SCORM file")</td>
    <td></td>
</tr>

Getting a date two days from today, format in d/m/yyyy

<tr>
    <td>storeEval</td>
    <td>var today=new Date(); Date.prototype.addDays = function(days) { var dat = new Date(this.valueOf()); dat.setDate(dat.getDate() + days); return dat; }; today.addDays(2)</td>
    <td>FutureDate</td>
</tr>
<tr>
    <td>storeEval</td>
    <td>storedVars.FutureDate.getDate() + '/' + (storedVars.FutureDate.getMonth() + 1) + '/' +&nbsp;&nbsp;storedVars.FutureDate.getFullYear()</td>
    <td>FutureDateFormatted</td>
</tr>

Turning a string into a recognised date

<tr>
    <td>storeEval</td>
    <td>"${DateOfBirth}".split("-")[0]</td>
    <td>DateOfBirthYear</td>
</tr>
<tr>
    <td>storeEval</td>
    <td>"${DateOfBirth}".split("-")[1]</td>
    <td>DateOfBirthMonth</td>
</tr>
<tr>
    <td>storeEval</td>
    <td>"${DateOfBirth}".split("-")[2]</td>
    <td>DateOfBirthDay</td>
</tr>
<tr>
    <td>storeEval</td>
    <td>new Date("${DateOfBirthYear}","${DateOfBirthMonth}"-1,"${DateOfBirthDay}");</td>
    <td>DateOfBirthAsDate</td>
</tr>

Another way to manipulate dates, in this case convert from International date format dd/mm/yyyy to US date format mm/dd/yyyy, on one line, is:

<!--Change Date to USA format-->
<tr>
    <td>storeEval</td>
    <td>storedVars['CrsEndDate'].split('/')[1]+&quot;/&quot;+storedVars['CrsEndDate'].split('/')[0]+'/'+storedVars['CrsEndDate'].split('/')[2]</td>
    <td>USDate</td>
</tr>

Identifying elements (tables, paragraphs etc) after a specific element, eg heading

This is very useful when tables have not been given an id or class in which to identify them.

<tr>
    <td>storeText</td>
    <td>//table[preceding::h2[.='Elements and Performance Criteria']]</td>
    <td>text</td>
</tr>

Identifying elements enclosed with other elements or with text/attributes

eg:

<button type="submit">
  <span>New</span>
</button>

Use this xpath:

//button[@type=‘submit’ and span=‘New’]

or:

//button[@type=‘submit’ and contains(., ‘New’)]

Removing stuff from words/numbers/strings

Beginning zeros

<tr>
    <td>storeEval</td>
    <td>storedVars.UoC_FieldofEducation.replace(/0*(\d+)/, &quot;$1&quot;);</td>
    <td>UoC_FieldofEducation</td>
</tr>

In case you need to remove white space from the beginning of strings

<tr>
    <td>storeEval</td>
    <td>storedVars.UoC_FieldofEducation.trim()</td>
    <td>UoC_FieldofEducation</td>
</tr>

Or you can use xpath:

translate(/tr/td/a, ' &#9;&#10;&#13', '')

or

translate(normalize-space(/tr/td/a), ' ', '')
Read more here

In case you need to remove numbers in parentheses

Read more here
<tr>
    <td>store</td>
    <td>javascript{storedVars.Course_RecognitionId.replace(/(\w+) \(\d+\)/, "");}</td>
    <td>Course_RecognitionId</td>
</tr>

Using maths with numbers (add, subtract etc)

The following shows adding GST to a price (an easier way might be to use “${Price}*1.1”)

<tr>
    <td>storeEval</td>
    <td>${Price}+(${Price}*(0.10))</td>
    <td>Added</td>
</tr>

Specify x decimal places

To specify 2 decimal places use the toFixed command in javascript.

<tr>
    <td>storeEval</td>
    <td>Number(storedVars['PriceExtrasLong']).toFixed(2);</td>
    <td>PriceExtrasLongFormatted</td>
</tr>

Putting an if clause in one line

This is useful otherwise you need lots of lines within a selenium ide test script.

<tr>
    <td>storeEval</td>
    <td>if (storedVars['EnterableCodePriceType'] == ''){storedVars['Discount'] = '0'};</td>
    <td>Discount</td>
</tr>

Modifying groupings of items eg Forms or Process Paths

When you want to modify a field settings on a form you need to use the field value as the reference. The following xpath will help identify the correct field and value to correct. Read more here

Add the examples below into this set of commands to make modifications to fields within CourseSales.com lists, eg to fields on forms and process steps on paths:

    <tr>
        <td>click</td>
        <td>//div/div/label[text()="Process Step"]/../input[@value = "ExamA ${TestId} ${UniqueId}"]/../../div/label[text()="Next Process Step if they Fail"]/../input[@type = "text"]</td>
        <td></td>
    </tr>
    <tr>
        <td>sendKeys</td>
        <td>//div/div/label[text()="Process Step"]/../input[@value = "ExamA ${TestId} ${UniqueId}"]/../../div/label[text()="Next Process Step if they Fail"]/../input[@type = "text"]</td>
        <td>${KEY_DEL}</td>
    </tr>
    <tr>
        <td>fireEvent</td>
        <td>//div/div/label[text()="Process Step"]/../input[@value = "ExamA ${TestId} ${UniqueId}"]/../../div/label[text()="Next Process Step if they Fail"]/../input[@type = "text"]</td>
        <td>blur</td>
    </tr>
    <tr>
        <td>click</td>
        <td>name=btnSave</td>
        <td></td>
    </tr>

To modify on the field ‘Customers Email’ the ‘Private’ checkbox on a form use this:

<tr>
    <td>check</td>
    <td>//div[label[normalize-space(text())="Field"] and input[@value="Customers Email"]]/preceding-sibling::div[label[text()="Private"]]/input</td>
    <td></td>
</tr>

To modify on the field ‘Customers Email’ the ‘Mandatory’ select options use this:

<tr>
    <td>select</td>
    <td>//div[label[normalize-space(text())="Field"] and input[@value="Customers Email"]]/preceding-sibling::div[label[text()="Mandatory"]]/select</td>
    <td>Private submit</td>
</tr>

To clear an autocomplete field on a Process Path based on the Process Step name use this:

<tr>
    <td>type</td>
    <td>//div[label[normalize-space(text())="Process Step"] and input[@value="ExamA ${TestId} ${UniqueId}"]]/following-sibling::div[label[text()=";Next Process Step if they Fail"]]/input[2]</td>
    <td></td>
</tr>

Change date from YYYYMMDD to D M YYYY

<tr>
    <td>storeEval</td>
    <td>new Array(&quot;January&quot;, &quot;February&quot;, &quot;March&quot;,&quot;April&quot;, &quot;May&quot;, &quot;June&quot;, &quot;July&quot;,&quot;August&quot;, &quot;September&quot;, &quot;October&quot;,&quot;November&quot;, &quot;December&quot;)</td>
    <td>monthNamesArray</td>
</tr>
<tr>
    <td>storeEval</td>
    <td>storedVars['CrsDateStartDate'].split('/')[2]</td>
    <td>CrsDateStartDateYear</td>
</tr>
<tr>
    <td>storeEval</td>
    <td>storedVars['CrsDateStartDate'].split('/')[1]</td>
    <td>CrsDateStartDateMonth</td>
</tr>
<tr>
    <td>storeEval</td>
    <td>storedVars['CrsDateStartDate'].split('/')[0]</td>
    <td>CrsDateStartDateDay</td>
</tr>
<tr>
    <td>storeEval</td>
    <td>parseInt(storedVars['CrsDateStartDateDay'],10)</td>
    <td>CrsDateStartDateDayFormatted</td>
</tr>
<tr>
    <td>storeEval</td>
    <td>storedVars['monthNamesArray'][storedVars['CrsDateStartDateMonth']-1]</td>
    <td>CrsDateStartDateMonthFormatted</td>
</tr>

Stripping innerHTML from other content incl. newlines

<tr>
    <td>storeEval</td>
    <td>window.document.getElementById(storedVars['CrsDateId']).textContent</td>
    <td>CategoryName</td>
</tr>
<tr>
    <td>storeEval</td>
    <td>storedVars.CategoryName.split(storedVars['VenueName'])[0]</td>
    <td>CategoryName</td>
</tr>
<tr>
    <td>storeEval</td>
    <td>storedVars.CategoryIdentifier.split(/\d{4}/)[1]</td>
    <td>CategoryName</td>
</tr>
<tr>
    <td>storeEval</td>
    <td>storedVars.CategoryIdentifier.split(/ /)[0]</td>
    <td>CategoryIdentifier</td>
</tr>

Pause for a bit…

<tr>
    <td>gotoIf</td>
    <td>'${NAT120ProgramIdentifier}' != 'UEE31710'</td>
    <td>continue</td>
</tr>
<tr>
    <td>storeEval</td>
    <td>alert('Program ${NAT120ProgramIdentifier} and the OptionId is ${QualificationOptionId} (should be 9677)')</td>
    <td></td>
</tr>
<tr>
    <td>pause</td>
    <td>10000</td>
    <td></td>
</tr>
<tr>
    <td>label</td>
    <td>continue</td>
    <td></td>
</tr>

Using the <enter> key

<tr>
    <td>sendKeys</td>
    <td>//input[contains(@id,'as-input')]</td>
    <td>${KEY_ENTER}</td>
</tr>
<tr>
    <td>type</td>
    <td>id=txtarea</td>
    <td>${TopicList}</td>
</tr>
<tr>
    <td>sendKeys</td>
    <td>id=txtarea</td>
    <td>${KEY_ENTER}</td>
</tr>

Replace the single quote with (without spaces) & # 39 ;

<tr>
    <td>store</td>
    <td>javascript{storedVars['CSVFile'].replace(/'/g, '&#39;');}</td>
    <td>CSVFile</td>
</tr>
<tr>
    <td>rollup</td>
    <td>Content</td>
    <td>Action=edit, OldName=Client Import CSV File, ContentType=Course Description, Name=Client Import CSV File, Label=Client Import CSV File, CourseCategory=, Location=, Status=, Short=${CSVFile}, Section1=Client Import CSV File - &lt;b&gt;Call this DocumentImport-testonly.csv&lt;/b&gt;Make Postcode 4 chars, phone numbers 10 chars, USI 10 chars, Section2=, Section3=, Section4=, Section5=, Section6=, Code=, CodeType=, CourseSalesInternal=</td>
</tr>
<tr>

Using arrays to create a CSV files for import

<tr>
    <td>store</td>
    <td>javascript{'&quot;'+storedVars['CourseDateIdArray'][storedVars['CourseCategoryIdentifierArray'].indexOf(storedVars['NAT120SubjectIdentifier'])]+'&quot;'}</td>
    <td>3RowToAdd</td>
</tr>
<tr>
    <td>store</td>
    <td>javascript{storedVars['3RowToAdd']+',&quot;'+storedVars['NAT085ClientFirstGivenNameArray'][storedVars['NAT085ClientIdentifierArray'].indexOf(storedVars['NAT120ClientIdentifier'])]+'&quot;'}</td>
    <td>3RowToAdd</td>
</tr>
<tr>
    <td>store</td>
    <td>javascript{storedVars['3RowToAdd']+',&quot;'+storedVars['NAT085ClientLastNameArray'][storedVars['NAT085ClientIdentifierArray'].indexOf(storedVars['NAT120ClientIdentifier'])]+'&quot;'}</td>
    <td>3RowToAdd</td>
</tr>

Array loop to check if we have already added this item

<!--Record if we have added this Module before (UoC Id, Name, Hours, Delivery Mode, Identifier)-->
<tr>
    <td>storeEval</td>
    <td>storedVars['NAT120SubjectIdentifier']+' '+storedVars['NAT060SubjectNameArray'][storedVars['NAT060SubjectIdentifierArray'].indexOf(storedVars['NAT120SubjectIdentifier'])]+' ('+storedVars['NAT120ProgramIdentifier']+'-'+storedVars['NAT120DeliveryModeIdentifier']+'-'+storedVars['NAT120ScheduledHours']+'-'+storedVars['NAT120AssociatedCourseIdentifier']+')'</td>
    <td>ModuleofTopic</td>
</tr>
<tr>
    <td>storeEval</td>
    <td>storedVars['DocumentSourceId']+' '+storedVars['NAT120SubjectIdentifier']+' '+storedVars['NAT120OutcomeIdentifierNational']+storedVars['NAT120OutcomeIdentifierTrainingOrganisation']+storedVars['NAT120ActivityStartDate']+' '+storedVars['NAT060SubjectNameArray'][storedVars['NAT060SubjectIdentifierArray'].indexOf(storedVars['NAT120SubjectIdentifier'])]+' ('+storedVars['NAT120ProgramIdentifier']+'-'+storedVars['NAT120DeliveryModeIdentifier']+'-'+storedVars['NAT120ScheduledHours']+'-'+storedVars['NAT120AssociatedCourseIdentifier']+')'</td>
    <td>TopicAdded</td>
</tr>
<!--Seek the ModuleAdded within the Array - does it exist?-->
<tr>
    <td>store</td>
    <td>javascript{storedVars['TopicAddedArray'].indexOf(storedVars['TopicAdded'])}</td>
    <td>TopicAddedTest</td>
</tr>
<!--Does the Module exist in the ModuleAddedArray?-->
<tr>
    <td>gotoIf</td>
    <td>'${TopicAddedTest}' != '-1'</td>
    <td>PastTopicList</td>
</tr>
<!--Add this module to the ModuleAddedArray-->
<tr>
    <td>push</td>
    <td>${TopicAdded}</td>
    <td>TopicAddedArray</td>
</tr>

Loop to add based on multiple pages

<!--Visit and edit/add to the Module-->
<tr>
    <td>setSpeed</td>
    <td>200</td>
    <td></td>
</tr>
<tr>
    <td>label</td>
    <td>CheckModulePage</td>
    <td></td>
</tr>
<tr>
    <td>waitForText</td>
    <td>id=pgTtl</td>
    <td>Options - List</td>
</tr>
<tr>
    <td>pause</td>
    <td>1000</td>
    <td></td>
</tr>
<!--Gather details to move through pages-->
<tr>
    <td>waitForElementPresent</td>
    <td>id=sp_1_pgjqLst</td>
    <td></td>
</tr>
<tr>
    <td>storeText</td>
    <td>id=sp_1_pgjqLst</td>
    <td>TotalPages</td>
</tr>
<tr>
    <td>storeValue</td>
    <td>css=input.ui-pg-input.ui-corner-all</td>
    <td>CurrentPage</td>
</tr>
<tr>
    <td>storeElementPresent</td>
    <td>xpath=(//tr[contains(td[1],normalize-space('${ModuleName}')) and contains(td[3], 'Active')])</td>
    <td>OptionExists</td>
</tr>
<tr>
    <td>gotoIf</td>
    <td>${OptionExists} == true</td>
    <td>PastAddModule</td>
</tr>
<!--If the module does not exist and there is more than one page go to that page and try again-->
<tr>
    <td>gotoIf</td>
    <td>${CurrentPage} ==${TotalPages}</td>
    <td>AddModule</td>
</tr>
<tr>
    <td>click</td>
    <td>css=span.ui-icon.ui-icon-seek-next</td>
    <td></td>
</tr>
<tr>
    <td>waitForNotText</td>
    <td>id=load_docTpcLst</td>
    <td>Loading...</td>
</tr>
<tr>
    <td>pause</td>
    <td>1000</td>
    <td></td>
</tr>
<tr>
    <td>gotoLabel</td>
    <td>CheckModulePage</td>
    <td></td>
</tr>
<!--Just start adding the module-->
<tr>
    <td>label</td>
    <td>AddModule</td>
    <td></td>
</tr>

Splitting at newline from list of course dates

<tr>
    <td>storeText</td>
    <td>//tr[(@id='${CrsDateId}') and not (contains(td[4], &quot;Cancelled&quot;))]/td[10]/span[@title=&quot;Edit this Course Date&quot;]/../../td[5]</td>
    <td>VenueName</td>
</tr>
<tr>
    <td>storeEval</td>
    <td>(storedVars.VenueName.split(/\u000A/)[1]).trim()</td>
    <td>VenueName</td>
</tr>

Useful tools

Selenium for automated testing

Add-ons to firefox

  • Power Debugger (Selenium IDE) - this gives you the ability to pause at errors, useful if you are adding data, as if the data fails the script pauses so you can correct it.

  • Stored Variables (Selenium IDE) - gives you the opportunity to check variables, like the cellcount or looptimes variables, to restart an import where it stopped.

  • Selenium IDE: Flow Control - includes goto, gotoIf and while loop functionality, used extensively in the CourseSales.com Selenium scripts. NOTE that Flow Control uses gotolabel NOT gotoLabel - the capitalisation difference is significant.

user-extension.js scripts (some of these are addons for firefox)

Add these to your Selenium IDE install in the Options>options menu

Online services

Applications & application extensions

  • OpenOffice calc extension to create CSV files from all sheets in a workbook

Firefox addons

Open office (or Libre Office) for data manipulation

To reduce the format Option1>Option2>Option3 down to Option3 for content course description names:

=RIGHT($Options.C7;LEN($Options.C7)-SEARCH(">";$Options.C7;SEARCH(">";$Options.C7;SEARCH(">";$Options.C7)+1)))

To do the same but for Option1>Option2 reduced down to Option2:

=RIGHT($Options.C27;LEN($Options.C27)-SEARCH(">";$Options.C27;SEARCH(">";$Options.C27)))

Filewriter

You can use filewriter in tests to create long files of exported data ready for import, this integrates seamlessly with Selenium scripts and is used, as an example, when creating document import files from NAT files. While you must log in to download files it is not necessary to log in to write the files. Upon a successful write the number of lines written is returned eg if you send 3 lines then 3 will be returned (in a div id=countRow).

Filewriter is accessible using the following URLS:

  • Append using get request (no form is used, note the newline delimiter is replaced with data[]=, representing each row, data[] are the rows without \n): https://<shortname>.coursesales.com/filewriter/append?filename=ccl.test-20170325c-Topic.csv&data[]=“2”,“this”,“and”,“that”&data[]=“2”,“here”,“and”,“there”

  • Append using textarea (note form is used, new rows separated by newline delimiter): https://<shortname>.coursesales.com/filewriter/appendform

  • Download a file (be signed in for this to work): https://<shortname>.coursesales.com/filewriter/download?filename=ccl.test-20170325c-Topic.csv

  • Delete (clear) a file: https://<shortname>.coursesales.com/filewriter/clear?filename=ccl.test-20170325c-Topic.csv

This functionality is very beta, so please let us know if you have trouble. Currently this is only available in the test environment. It is possible to write to the same file using append and appendform.

Example use in Selenium

<tr>
    <td>open</td>
    <td>${CurrentEnv}/filewriter/append?filename=${URLName}-Topic${TopicListRowToSend}</td>
    <td></td>
</tr>
<tr>
    <td>verifyNotText</td>
    <td>css=h1</td>
    <td>Request-URI Too Long</td>
</tr>
<tr>
    <td>verifyNotText</td>
    <td>css=h1</td>
    <td>Bad Request</td>
</tr>
<tr>
    <td>open</td>
    <td>${CurrentEnv}/filewriter/appendform</td>
    <td></td>
</tr>
<tr>
    <td>type</td>
    <td>name=filename</td>
    <td>${URLName}-Topic</td>
</tr>
<tr>
    <td>type</td>
    <td>name=datarows</td>
    <td>${TopicListRowToSend}</td>
</tr>
<tr>
    <td>click</td>
    <td>css=input[type=&quot;submit&quot;]</td>
    <td></td>
</tr>
<tr>
    <td>waitForElementPresent</td>
    <td>id=countRow</td>
    <td></td>
</tr>

​RegEx used to help clean data up.

^(.+?)@ - used to find (and then replace?) the first part of an email address, the ‘user’ part, eg when creating website URLs from emails.