Selenium and JavaScript: Accessing multiple elements with the same ID - selenium

I've got a page (Angular8) with multiple button/input elements having the same ID.
The ID's are, in the order the appear on the page:
1: for="lastOppDokument-0-VERGE-LEGITIMASJON"
2: for="lastOppDokument-1-VERGE-VERGEMAL"
...
3: for="lastOppDokument-0-VERGE-LEGITIMASJON"
4: for="lastOppDokument-1-VERGE-VERGEMAL"
The elements on lines 1,3 and 2,4 have the same ID.
I'm trying to access the elements using XPath and index like this:
driver.findElement(By.xpath("(//input[#for='lastOppDokument-0-VERGE-LEGITIMASJON'])[1]
driver.findElement(By.xpath("(//input[#for='lastOppDokument-0-VERGE-LEGITIMASJON'])[2]
If I check the size:
int x = driver.findElements(By.xpath("(//input[#for='lastOppDokument-0-VERGE-LEGITIMASJON'])")).size();
it says "2", which is correct.
However, when I try to click those two buttons, the first one (LINE 1) is clicked, but then the next button on the page (LINE 2) i clicked istead of the one on LINE 3.
There's obviously something wrong with my XPath expression, but what? Also, the page is an Angular page, and in the markup code "for" is used to automatically index the ID. But selenium finds the elements (albeit not all the correct ones) using ID. But maybe I need to use something else to identify the correct elements?
UPDATE:
To troubleshoot, I'm skipping the use of a loop and just trying to access the two elements manually:
WebElement buttonToClick = driver.findElement(By.xpath("(//input[#id='lastOppDokument-0-VERGE-LEGITIMASJON'])[1]"));
filUtils.uploadFile(buttonToClick, legitimasjonfil);
WebElement buttonToClick2 = driver.findElement(By.xpath("(//input[#for='lastOppDokument-0-VERGE-LEGITIMASJON'])[2]"));
filUtils.uploadFile(buttonToClick2, legitimasjonfil);
The uploadFile (and related) methods:
public void uploadFile(WebElement id, String filename) {
id.sendKeys(getAbsolutePathToTestFile(TESTFILER_PATH + "/" + filename));
}
private String getAbsolutePathToTestFile(String path) {
return copyFileToTargetTempPath(path, path);
}
private String copyFileToTargetTempPath(String originPath, String destinationPath) {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(originPath);
File destination = new File(TARGET_TEMP_PATH + destinationPath);
try {
assert resourceAsStream != null;
org.apache.commons.io.FileUtils.copyInputStreamToFile(resourceAsStream, destination);
} catch (IOException e) {
throw new RuntimeException(e);
}
return destination.getAbsolutePath();
}
UPDATE 2:
<td>
<span class="hb-felt knappesamling-gruppe hb-avstandIngen ng-star-inserted">
<input class="hb-lastOppFil-input hb-bare-skjermleser" type="file" id="lastOppDokument-0-VERGE-LEGITIMASJON"
accept=".pdf,.bmp,.gif,.jpeg,.jpg,.png,.tiff">
<label class="hb-knapp hb-knapp--standard hb-spinner-tekst" for="lastOppDokument-0-VERGE-LEGITIMASJON"> Velg fil </label><!---->
</span>
</td>
<td>
<span class="hb-felt knappesamling-gruppe hb-avstandIngen ng-star-inserted">
<input class="hb-lastOppFil-input hb-bare-skjermleser" type="file" id="lastOppDokument-1-VERGE-VERGEMAL"
accept=".pdf,.bmp,.gif,.jpeg,.jpg,.png,.tiff"><label class="hb-knapp hb-knapp--standard hb-spinner-tekst" for="lastOppDokument-1-VERGE-VERGEMAL"> Velg fil </label><!----></span>
<label class="hb-knapp hb-knapp--standard hb-spinner-tekst" for="lastOppDokument-1-VERGE-VERGEMAL"> Velg fil </label>
</span>

Related

Why Am Getting Unable To Locate Element Error?

I Have Divided The Xpath into 4 Parts Because in Xpath only one Row Value Changes That is /tr[2] Like that 3,4,5 upto the Path Ends.
public class NewTest {
#Test
public void f() throws InterruptedException
{
System.setProperty("webdriver.chrome.driver", "F:\\New folder\\chromedriver.exe");
WebDriver driver = new ChromeDriver();
driver.get("http://xxxxxxxxxxx/index.aspx");
driver.manage().window().maximize();
WebElement ee= driver.findElement(By.id("ddlstore"));
Select s11 = new Select(ee);
s11.selectByVisibleText("xxxxxx");
String s1 = "/html[1]/body[1]/form[1]/div[5]/center[1]";
String s2 = "/div[1]/table[1]/tbody[1]/tr[";
String s3 ="]";
String s4 = "/td[11]/input[1]";
for(int i=2;i<=99;i++)
{
String finalXpath= s1+s2+i+s3+s4;
driver.findElement(By.xpath(finalXpath)).click();
Thread.sleep(3000);
try {
WebElement e1 = driver.findElement(By
.xpath("//p[#id='skipcount']"));
System.out.println(e1.getText());
WebElement e2 = driver.findElement(By.xpath("/html/body/div[1]/div/div"));
WebElement e3 = driver.findElement(By.xpath("/html/body/div[1]/div/div/div[3]/button[2]"));
Actions a1 = new Actions(driver);
a1.moveToElement(e2).click(e3).build().perform();
} catch (Exception e) {
System.out.println("Can't Click on The Element ");
}
}
The Element is Not Clicking it is Showing an Error , Unable to Locate Element,Xpath
Html Code For The First Xpath Divided into String :
<input type="submit" name="CustomPaging_GridView$ctl02$ff" value="SKIP" onclick="product_skip(this);" id="CustomPaging_GridView_ff_0" class="button2" data-id="12691247570" data-id1="36025">
Html Code For Xpath Which is in Try/Catch Block:
<div class="modal-footer">
<span id="prcid" style="display:none;">processing...</span>
<button type="button" id="skipok" onclick="skipoverall(this)" class="btn btn-primary" data-id="12691247570" data-id1="36025">Ok</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
</div>
Ps : Actually The Doubt is, I Need To Click an Skip Button Which has an Same Xpath For All Button and Each Time When i Click Skip Button an Popup Will Appear in That i Need To Click The Cancel Button
Try this :
int i = 1;
String s2 = "/div[1]/table[1]/tbody[1]/tr[' " +i+ " '];
There is no need of String s3.
Try this:
// you don't need 'e2', instead you hover 'e3' and click on it
WebElement e3 = driver.findElement(By.xpath("/html/body/div[1]/div/div/div[3]/button[2]"));
Actions a1 = new Actions(driver);
a1.moveToElement(e3).click(e3).build().perform();
Suggestion:
String s1 = "/html[1]/body[1]/form[1]/div[5]/center[1]";
String s2 = "/div[1]/table[1]/tbody[1]/tr[";
String s3 ="]";
String s4 = "/td[11]/input[1]";
for(int i=2;i<=99;i++)
{
String finalXpath= s1+s2+i+s3+s4;
can be replaced with:
for(int i=2;i<=99;i++)
{
String finalXpath= "/html[1]/body[1]/form[1]/div[5]/center[1]/div[1]/table[1]/tbody[1]/tr[" + i + "]/td[11]/input[1]";
...

Using document.getElementsByClassName in Testcafe

I have a menu that always has the same structure, but the IDs can change from one installation to another. the only thing that stays the same is the heading (in my case "Plugins"). I call the document.getElementsByClassName function with a Selector inside my test:
var slides = Selector(() =>{
return document.getElementsByClassName("c-p-header-text");
});
Every heading of an menu element has the c-p-header-text class. Here is what a menu heading element looks like:
<div id="ext-comp-1002" class="c-p c-tree c-p-collapsed" style="width: auto;">
<div class="c-p-header c-unselectable c-accordion-hd" id="ext-gen135" style="cursor: pointer;">
<div class="c-tool c-tool-toggle" id="ext-gen140"> </div>
<img src="/backEnd/images/s.gif" class="c-p-inline-icon order"><span class="c-p-header-text" id="ext-gen150">Plugins</span>
</div>
It would be easy to use await t.click("#ext-gen150") but it is not safe that it is always this id.
here is what i tried:
await t
.click('#sites__db');
var slides = Selector(() =>{
return document.getElementsByClassName("c-p-header-text");
});
console.log("[DEBUG]" + slides);
console.log("[DEBUG] found " + slides.length + " elements");
for(var i = 0; i < slides.length; i++)
{
var txtOfCurrElem = slides.item(i).innerText;
console.log("[DEBUG "+ i +"] Text: " + txtOfCurrElem);
}
Running this test give me the following output:
[DEBUG]function __$$clientFunction$$() {
var testRun = builder.getBoundTestRun() || _testRunTracker2.default.resolveContextTestRun();
var callsite = (0, _getCallsite.getCallsiteForMethod)(builder.callsiteNames.execution);
var args = [];
// OPTIMIZATION: don't leak `arguments` object.
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}return builder._executeCommand(args, testRun, callsite);
}
[DEBUG] found 0 elements
The plan is to find the element (with the heading "Plugins") and then click on it when the test continuous.
You don't have to use document.getElementsByClassName in this case. You can just use CSS class selector instead:
var slides = Selector('.c-p-header-text');
You should use the count property when dealing with an array of Selectors. docs. Also, element properties, like exists, count, and DOM node state properties are Promisified, so when you use them not in t.expect, you should use the await keyword:
var count = await slides.length;
console.log("[DEBUG] found " + count + " elements");
for(var i = 0; i < count; i++)
{
var txtOfCurrElem = await slides.nth(i).innerText;
console.log("[DEBUG "+ i +"] Text: " + txtOfCurrElem);
}
I found a simple answer to my question. I use the .withText option to click on the Plugins element:
.click(Selector('span').withText("Plugins"))
Since this name is also unique, it is always the correct element that gets clicked. I do not know if it would have worked with the solution from #AndreyBelym if my site is not an extJS web application.

font family is not working in PDF

I'm converting HTML to PDF as below:
public const string PdfDocumentHeaderHtml = #"<!DOCTYPE html>
<html lang='en' xmlns='http://www.w3.org/1999/xhtml'>
<head>
<meta charset='utf-16' />
<title></title>
</head>
<body>
<table>
<tr>
<td colspan='3'>
<span Style='font-family:Arial;font-size:10pt;font-weight:bold;'>{0}</span>
<br/>
<br/>
<span class='pageHeaderText'>{1}</span>
</td>
<td colspan='1'>
<span><img src='' width='150' height='90' alt='NOS'/></span>
</td>
</tr>
</table>
</body>
</html>";
And save to PDF using the below code:
public override void OnCreatePDF(PdfWriter writer, Document document)
{
iTextSharp.text.FontFactory.Register(#"C:\Windows\Fonts\arial.ttf", "Arial");
base.OnCreatePDF(writer, document);
if (writer == null)
throw new ArgumentNullException("writer");
if (document == null)
throw new ArgumentNullException("document");
var headerHtml = string.Format(Constants.NosPdfDocumentHeaderHtml, Urn, Title);
var providers = new Dictionary<string, Object> { { HTMLWorker.IMG_BASEURL, string.Format(Constants.HeaderImageLocation, SiteUrlForHeaderImage) } };
List<IElement> htmlarraylist = HTMLWorker.ParseToList(new StringReader(headerHtml), null, providers);
foreach (IElement htmlElement in htmlarraylist)
{
document.Add(htmlElement);
document.Add(new LineSeparator((float)0.90, 100, new BaseColor(0, 112, 192, 0), 0, 0));
}
}
I want to set Font-Family:Arial for the PDF but the problem is, when I see the PDF-File properties, it says Helvetica is used.
I think I need to download Adobe Font Metric file (arial.afm file) and set this font family (instead of arial.ttf) for use with pdf. But I don't know how to do it.
Could you please advice?
Thanks,
In the comment section, you are asking for an alternative to add a table structure to a document.
That's easy with PdfPTable. For instance, if I want to create a table with 3 columns, I do:
PdfPTable table = new PdfPTable(3);
I want to span 100% of the available width between the margins of the page, so I do:
table.WidthPercentage = 100;
I want the first column to be twice as wide as column two and three, so I do:
table.SetWidths(new int[]{2, 1, 1});
Now I add cells:
PdfPCell cell;
cell = new PdfPCell(new Phrase("Table 1"));
cell.Colspan = 3;
table.AddCell(cell);
cell = new PdfPCell(new Phrase("Cell with rowspan 2"));
cell.Rowspan = 2;
table.AddCell(cell);
table.AddCell("row 1; cell 1");
table.AddCell("row 1; cell 2");
table.AddCell("row 2; cell 1");
table.AddCell("row 2; cell 2");
Finally, I add the table to the Document:
document.Add(table);
And that's it.

Update html from csv

I have a quite long html menu list (circa 30 menu positions) where I am trying to dynamically populate with labels according to a .csv file. I’d like to change the each menu label i.e. “the Slots”. Each time the page is loaded, I’ll need to look up position1, and return the right label and Title tag in the page I'm pointing to. There are many fixed positions, but when the .csv file is updated, the menu label needs to change accordingly. To add fuel to my fire, I need to hide a menu if the position in the first column of the .csv cannot be found. This absolute amateur would appreciate any insight you might have.
CSV file saved into directory
position1,Slot1
position2,Slot2
position3,Slot3
position4,Slot4
HTML
<a href='link'>
<span class='hidden-minibar'>Slot 1 </span>
</a>
</li>
<a href='gohere'>
<span class='hidden-minibar'>Slot 2 </span>
</a>
</li>
<a href='link'>
<span class='hidden-minibar'>Slot 3 </span>
</a>
</li>
<a href='link'>
<span class='hidden-minibar'>Slot 4 </span>
</a>
</li>
if (window.FileReader) {
var reader = new FileReader();
reader.onload = function(filedata) {
var list = filedata.target.result;
csvParser(list);
};
reader.readAsText(this.selectedfile.target.files.item(0));
}
var csvParser = function(list) {
var quoteRegexp = new RegExp("^\"(.*)\"$");
var data = [];
var lines = list.split(new RegExp("\r?[\r\n]"));
for (var iCtr = 0; iCtr < lines.length; iCtr += 1) {
var fields = lines[iCtr].split(",");
for (var jCtr = 0; jCtr < fields.length; jCtr += 1) {
fields[jCtr] = fields[jCtr].replace(quoteRegexp, "$1");
}
data.push(fields);
}
return data;
}
use FileReader object and read csv file on client site
use csvParser function to parse row csv data
create dynamic html and appropriate logic on csvParser function result data.
Basic csv read Example
http://jsfiddle.net/techrevolt/W8fME/

selecting option from hidden drop down menu

<table id="rusTable" class="groupTable" cellspacing="0" cellpadding="0">
<tbody class="ui-sortable" style="">
<tr class="groupTop ruBorder" style="display: table-row;">
<tr id="ru0" class="siru">
<tr class="ruOp off">
<td class="first"></td>
<td colspan="3">
<select class="ruOpSelect">
<option></option>
<option value="AND">AND</option>
<option>AND NOT</option>
<option>OR</option>
</select>
</td>
<td class="last"></td>
</tr>
<tr id="ru1" class="siru">
<tr class="ruOp off">
<td class="first"></td>
<td colspan="3">
<td class="last"></td>
</tr>
<tr id="ru2" class="siru">
<tr class="groupBtm ruBorder" style="display: table-row;">
</tbody>
<tfoot>
</table>
I want to select the AND option
Selenium webdriver code
actions.moveToElement(driver.findElement(By.xpath("//*#id='ruTable']/tbody/tr[3]/td[2]"))).build().perform();
waitForElement(By.xpath("(//*[#id='ruTable']//*[contains(#class,'ruOpSelect')])[1]"),30);
new Select(driver.findElement(By.xpath("(//*[#id='ruTable']//*[contains(#class,'ruOpSelect')])[1]"))).selectByVisibleText("AND");
It does hover action but does not select anything from drop down menu
ERROR - Timed out after 30 seconds waiting for visibility of element located by By.xpath:
(//*[#id='ruTable']//*[contains(#class,'ruOpSelect')])[1]
Try using this xpath, just call click on this:
"//select[#class='ruOpSelect']/option[text()='AND']"
Any drop downs I generally start with the "select" in my xpath and next level should be option.
I use
Thread.sleep(3000);
after the moveToElement action. Works for me. Then I suppose you need to click the element to select it.
You can use this code to select the particular option
IdentifyBy By;
waitForDropDownEnable(By.xpath, "xpath");
WebElement element = findElement(BY.xpath("xpath for the element"));
Select select = new Select(element);
List<WebElement> options = select.getOptions();
String values = "";
for(int index=0; index<options.size(); index++) {
if(!values.equals("")) {
values += ", ";
}
values += options.get(index).getText();
}
select.selectByVisibleText(value);
public void waitForDropDownEnable(final IdentifyBy idBy, final String controlDesc) {
int timeout =30 * 1000;
final long MAX_TIME_OUT = 300000;
final long DELAY = 250;
final long DEAD_LINE = System.currentTimeMillis() + MAX_TIME_OUT;
boolean isEnabled = false;
try {
while(System.currentTimeMillis() <= DEAD_LINE) {
getWebDriver().manage().timeouts().implicitlyWait(0,TimeUnit.SECONDS);
if(findElement(By.xpath("")).isEnabled()) {
isEnabled = true;
break;
}
Thread.sleep(DELAY);
}
} catch (WebDriverException wdex) {
;;
} catch(Exception ex) {
;;
}
}
}
selecting option from hidden drop down menu
in such case i use jsExecutor. Always works for me:
String cssLocator = "tbody.ui-sortable tr.ruOp off td select.ruOpSelect option[value="AND"]";
//find css locator of the needed AND element in dropdown. I use firepath (addon to firebug).
JavascriptExecutor js = (JavascriptExecutor) driver;
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("var x = $(\'"+cssLocator+"\');");
stringBuilder.append("x.click();");
js.executeScript(stringBuilder.toString());
Hope this works for you