Back on May 19th 2021, I wrote this Q&A regarding recent (Apr-May-21) suspected changes to an interface in relation to mshtml.dll and late bound referencing. This is a part 2, if you will.
Previously, in questions such as this and this, I have remarked upon the lack of support for various CSS selectors with mshtml.dll, in particular regarding pseudo-classes. In the aforementioned questions, I highlighted that nth-child() and nth-of-type() were not implemented with respect to MSHTML.
Typically, as demonstrated here, not supported selector syntax can result in:
Run-time error '-2140143604 (8070000c)': Could not complete the
operation due to error 8070000c.
I expect some things to break as various versions/platforms are no longer supported in relation to Internet Explorer (IE) (which MSHTML is related to - see my this. What I did not expect
to find was a recent improvement in supported CSS selectors. Take the following example:
Option Explicit
''Required references:
'' Microsoft HTML Object Library
Public Sub CssTest()
Const URL = "https://books.toscrape.com/"
Dim html As MSHTML.HTMLDocument
Set html = New MSHTML.HTMLDocument
With CreateObject("MSXML2.XMLHTTP")
.Open "GET", URL, False
.send
html.body.innerHTML = .responseText
End With
Debug.Print html.querySelector("meta:nth-of-type(2)").outerHTML
End Sub
Prior to Apr-May'21, this would have errored out due to the use of non-implemented syntax.
Now, on my set-up, where I saw an update to mshtml.dll during early May (latest), I get the same result as had I run this via an automated Internet Explorer instance, where it was already supported:
<meta name="created" content="24th Jun 2016 09:29">
So, what are the currently supported CSS selectors available to VBA?
I have covered the 'why do we care?' in the previous Q&A so won't repeat here. I will however, re-state my set-up:
My set-up:
OS Name Microsoft Windows 10 Pro
Version 10.0.19042 Build 19042
System Type x64-based PC
Microsoft® Excel® 2019 MSO (16.0.13929.20206) 32-bit (Microsoft Office Professional Plus)
Version 2104 Build 13929.20373
mshtml.dll file 11.00.19041.985
ieframe.dll file 11.0.19041.964
Feedback:
As with the prior Q&A, any feedback on set-ups which do/do not see these changes I would appreciate. I will add feedback to this for others to be able to reference.
tl;dr;
There is much greater support for css selectors and for Element.querySelector (allowing for greater flexibility in chaining querySelector(All) calls. This enormously enhances the expressivity of the MSHTML class, in terms of CSS selectors, and brings it on par with Selenium Basic.
Motivation:
I have been wanting to write a list of supported selectors for some time, due to the lack of documentation on this in relation to VBA, and the trial and error nature of learning what does and doesn't work. This latest change has spurred me to do so, and include those libraries which currently support use of CSS selectors within them.
CAVEATS:
This is not exhaustive; it is pretty comprehensive.
Should you find any errors, particularly with respect to Selenium Basic, which I had to write from memory, please notify me and I will edit accordingly.
The recent changes, represented by shaded cells within the summary table (JSFiddle)| marked with ✔* , within simplified table below, are as they pertain to my set-up, at this point in time. Your mileage may vary e.g. CSS selectors were not supported at all < IE8.
Before and After:
Traditionally, the expressivity of CSS selectors within VBA was as follows, with respect to the libraries supporting them:
Selenium implementing, by far, the most CSS selectors.
Current state:
The current state of implemented selectors I believe to be as follows (sorry for image quality, even when you click to enlarge table - please see JSFiddle for clearest table view):
I include this as a simplified HTML insert as well, so you can click on hyperlinks. Please click the Run code snippet below the code insert, then the Full page link. Apologies, the table is large and I haven't even covered all conceivable selectors - only the main ones I consider likely to be frequently of use. Inserting a fancy table threw me over the body character limit so here we are. For a fancy table please see this JSFiddle - the newly supported are shaded.
<!DOCTYPE html>
<html>
<head>
<title>VBA: Valid CSS Selectors 2021-05-30</title>
</head>
<body>
<h1>VBA: Valid CSS Selectors 2021-05-30</h1>
<table>
<tr>
<td colspan="2">
Selectors Level 3 Specification
</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Pattern</td>
<td>Represents</td>
<td>Description</td>
<td>Level</td>
<td>Microsoft HTML Object Library (MSHTML)</td>
<td>Microsoft Internet Explorer Controls (SHDocVw)</td>
<td>Selenium Type Library (Selenium)</td>
<td>Remarks</td>
</tr>
<tr>
<td>*</td>
<td>any element</td>
<td>
Universal selector
</td>
<td>2</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E</td>
<td>an element of type E</td>
<td>
Type selector
</td>
<td>1</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E[foo]</td>
<td>an E element with a "foo" attribute</td>
<td>
Attribute selectors
</td>
<td>2</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E[foo="bar"]</td>
<td>an E element whose "foo" attribute value is exactly equal to "bar"</td>
<td>
Attribute selectors
</td>
<td>2</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E[foo~="bar"]</td>
<td>an E element whose "foo" attribute value is a list of whitespace-separated values, one of which is exactly equal to "bar"</td>
<td>
Attribute selectors
</td>
<td>2</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E[foo^="bar"]</td>
<td>an E element whose "foo" attribute value begins exactly with the string "bar"</td>
<td>
Attribute selectors
</td>
<td>3</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E[foo$="bar"]</td>
<td>an E element whose "foo" attribute value ends exactly with the string "bar"</td>
<td>
Attribute selectors
</td>
<td>3</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E[foo*="bar"]</td>
<td>an E element whose "foo" attribute value contains the substring "bar"</td>
<td>
Attribute selectors
</td>
<td>3</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E[foo|="en"]</td>
<td>an E element whose "foo" attribute has a hyphen-separated list of values beginning (from the left) with "en"</td>
<td>
Attribute selectors
</td>
<td>2</td>
<td>x</td>
<td>x</td>
<td>x</td>
<td> </td>
</tr>
<tr>
<td>E[attr operator value i]</td>
<td>value compared case-insensitively (ASCII range).</td>
<td>
Attribute selectors
</td>
<td>4</td>
<td>x</td>
<td>x</td>
<td>?</td>
<td>
i identifier
</td>
</tr>
<tr>
<td>E[attr operator value s]</td>
<td>value compared case-sensitively (ASCII range).</td>
<td>
Attribute selectors
</td>
<td>4</td>
<td>x</td>
<td>x</td>
<td>x</td>
<td>
s identifier
</td>
</tr>
<tr>
<td>E:root</td>
<td>an E element, root of the document</td>
<td>
Structural pseudo-classes
</td>
<td>3</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td>HTML node only</td>
</tr>
<tr>
<td>E:nth-child(n)</td>
<td>an E element, the n-th child of its parent</td>
<td>
Structural pseudo-classes
</td>
<td>3</td>
<td>✔*</td>
<td>✔</td>
<td>✔</td>
<td>nth-child(odd) and (even) as well as nth-child(range) also supported</td>
</tr>
<tr>
<td>E:nth-last-child(n)</td>
<td>an E element, the n-th child of its parent, counting from the last one</td>
<td>
Structural pseudo-classes
</td>
<td>3</td>
<td>✔*</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E:nth-of-type(n)</td>
<td>an E element, the n-th sibling of its type</td>
<td>
Structural pseudo-classes
</td>
<td>3</td>
<td>✔*</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E:nth-last-of-type(n)</td>
<td>an E element, the n-th sibling of its type, counting from the last one</td>
<td>
Structural pseudo-classes
</td>
<td>3</td>
<td>✔*</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E:first-child</td>
<td>an E element, first child of its parent</td>
<td>
Structural pseudo-classes
</td>
<td>2</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E:last-child</td>
<td>an E element, last child of its parent</td>
<td>
Structural pseudo-classes
</td>
<td>3</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E:first-of-type</td>
<td>an E element, first sibling of its type</td>
<td>
Structural pseudo-classes
</td>
<td>3</td>
<td>✔*</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E:last-of-type</td>
<td>an E element, last sibling of its type</td>
<td>
Structural pseudo-classes
</td>
<td>3</td>
<td>✔*</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E:only-child</td>
<td>an E element, only child of its parent</td>
<td>
Structural pseudo-classes
</td>
<td>3</td>
<td>✔*</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E:only-of-type</td>
<td>an E element, only sibling of its type</td>
<td>
Structural pseudo-classes
</td>
<td>3</td>
<td>✔*</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E:empty</td>
<td>an E element that has no children (including text nodes)</td>
<td>
Structural pseudo-classes
</td>
<td>3</td>
<td>✔*</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E:link</td>
<td rowspan="2">an E element being the source anchor of a hyperlink of which the target is not yet visited (:link) or already visited (:visited)</td>
<td rowspan="2">
The link pseudo-classes
</td>
<td>1</td>
<td>✔*</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E:visited</td>
<td>1</td>
<td>✔*</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E:not(s)</td>
<td>an E element that does not match simple selector s</td>
<td>
Negation pseudo-class
</td>
<td>3</td>
<td>✔*</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E F</td>
<td>an F element descendant of an E element</td>
<td>
Descendant combinator
</td>
<td>1</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E > F</td>
<td>an F element child of an E element</td>
<td>
Child combinator
</td>
<td>2</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E + F</td>
<td>an F element immediately preceded by an E element</td>
<td>
Next-sibling combinator
</td>
<td>2</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>E ~ F</td>
<td>an F element preceded by an E element</td>
<td>
Subsequent-sibling combinator
</td>
<td>3</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td>foo, bar</td>
<td>foo, bar will match both <foo> and <bar> elements.</td>
<td>
Selector list
</td>
<td>1</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td> </td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>element.querySelector</td>
<td>Expanded element.querySelector</td>
<td>
Element.querySelector
</td>
<td>API</td>
<td>✔</td>
<td>✔</td>
<td>✔</td>
<td>Can now chain querySelector(All) calls on wider base node range</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Lib info:</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>Microsoft HTML Object Library (MSHTML)</td>
<td>MS Internet Explorer Controls (SHDocVw)</td>
<td>Selenium Type Library (Chromedriver)</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Lib</td>
<td>mshtml.dll</td>
<td>ieframe.dll</td>
<td>selenium.dll</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>File Version</td>
<td>11.00.19041.985</td>
<td>11.0.19041.964</td>
<td>2.0.9.0</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Date</td>
<td>2021-05-12</td>
<td>2021-05-12</td>
<td>2016-03-02</td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</body>
</html>
12 newly supported pseudo-classes and an expanded Element.querySelector:
If you run the above snippet, and view full page, you will see there are now, at least, 12 newly supported pseudo-classes supported, as well as mention of expanded Element.querySelector. Bam, kapow, ker-sploosh, shut the proverbial front door ... welcome to VBA CSS Canaan, Scraper's Shangri-la, Nerd Nirvana!
I think there may also have been interesting updates to ieframe.dll; the focus here is on recent mshtml.dll changes. You may wish to review the IE support under the Lifecyle announcements here and here, or search for Lifecycle FAQ - Internet Explorer and Microsoft Edge.
As the benefit of the expanded Element.querySelector() was not covered in the last Q&A, I will briefly mention it here. By expanded, I mean an increased number of elements which you can call querySelector on, such that you can chain .querySelector() i.e .querySelector(..).querySelector(..) and .querySelector(..).querySelectorAll(..).
Previously, this was largely not possible. As exemplified by this question. Typically, the workaround was to chain traditional methods onto the returned node e.g.
html.querySelector("body").getElementsByTagName("li"); this led to unsightly chaining and hard to follow, as well as limited, paths to target elements. Better, IMHO, was the idea of a surrogate MSHTML.HTMLDocument variable, which would carry the innerHTML of the current node returned by querySelector, and thus allow you to call querySelector(All) again; and thereby gain access to much faster matching, clearer syntax and greater versatility. Numerous examples of that approach here.
End Notes:
This is a document under revision. All feedback on improvements welcomed.
Thanks:
Finally, a big thanks to #SIM for running a test script of mine to examine this on a different set-up.
Related
My goal is to find the text contains in the first preceding-sibling which match.
My xpath is the following one : //*[#id='myid']/parent::td(/preceding-sibling::td/label/a)[1]
Here I want the text of the td/label/a the most close of the parent of //*[#id='myid'].
Can you tell me if you see any mistake please? Thank you a lot.
Update 1
This one is working //*[#id='myid']/parent::td/preceding-sibling::td[2]/label/a but it is not everytime 2, sometimes it can be 6 or whatever.
Update 2
Example 1
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td>
<label>
<a>text</a>
</label>
</td>
<td></td>
<td>
<input id='myid'>
</td>
</tr>
</tobdy>
Example 2
<tbody>
<tr>
<td></td>
<td>
<label>
<a>text</a>
</label>
</td>
<td></td>
<td></td>
<td></td>
<td>
<input id='myid'>
</td>
</tr>
</tobdy>
Example 3
<tbody>
<tr>
<td></td>
<td>
<label>
<a>text</a>
</label>
</td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
<td>
<input id='myid'>
</td>
</tr>
</tobdy>
I don't think you'd need to go up to any ancestors; just go to the first preceding td (that contains a label/a)...
//input[#id='myid']/preceding::td[label/a][1]/label/a
You can use xpath below. Get first parent tr and it's child label/a
//input[#id='myid']/ancestor::tr[1]//label/a
One more way is to use union operator in xpath with all locators for all cases in order you need:
//input[#id='myid']/ancestor::td/preceding-sibling::td/label/a | //input[#id='myid']/ancestor::tr[1]/preceding-sibling::tr//label/a
Seems like table content are dynamic as i can see in your examples but <tbody> must be one for that table.
(//input[#id='myid']/preceding::td/label/a)[last()]
I'm trying to parse data from rows which occasionally have a colspan=2 which spoils my ability to target data to extract.
What I'd like to do is remove the 'colspan=2' from the table element every time it occurs:
#replace
<td colspan="2" class="time">10:00 AM</td>
#with
<td>635</td>
Is this possible? And can I work it into a conditional if then else?
Here's a more verbose example:
<table>
<tr class="playerRow even">
<td class="pos">1</td>
<td><span class="rank"></span> -</td>
<td class="player"><p class="playerName">John doe</p></td>
<td class="background">X</td>
<td>345</td> #THIS ELEMENT FREQUENT
<td></td>
<td></td>
<td></td>
<td></td>
<td style=""></td>
</tr><
<tr class="playerRow odd">
<td class="pos">1</td>
<td><span class="rank"></span> -</td>
<td class="player"><p class="playerName">John doe</p></td>
<td class="background">X</td>
<td colspan="2" class="myClass" style="">3:15 PM</td> #THIS ELEMENT OCCASIONAL
<td></td>
<td></td>
<td></td>
<td></td>
<td style=""></td>
</tr>
<tr class="playerRow odd">
<td class="pos">1</td>
<td><span class="rank"></span> -</td>
<td class="player"><p class="playerName">John doe</p></td>
<td class="background">X</td>
<td>22</td> #THIS ELEMENT FREQUENT
<td></td>
<td></td>
<td></td>
<td></td>
<td style=""></td>
</tr>
</table>
So whenever I come across the colspan I'd like to replace it with a plain td, so it doesn't shunt the row elements across and mess up my count.
This will convert:
<td colspan="2" class="myClass" style="">3:15 PM</td>
to:
<td>3:15 PM</td>
from bs4 import BeautifulSoup
bs = BeautifulSoup(html)
for x in bs.findAll("td"):
if "colspan" in x.attrs:
x.attrs = {}
Do you want it to remove the value also?
Having a semantics issue. I have a basic table with a standard header and footer. Each row contains an order, beneath each row I need to display another table, that will contain a break down of costs relating to that order. Additionally, these inner tables will be displayed with a jQuery accordion to hide and show when required (but I'm just concentrating on the HTML for now)
How can I semantically approach this in HTML?
<table>
<thead>
<th>Package number</th>
<th>Date placed</th>
<th>Placed by</th>
<th>Total cost</th>
</thead>
<tr>
<td>1</td>
<td>Weds</td>
<td>Jonno</td>
<td>£15</td>
</tr>
<tr>
<td colspan="4">
<table>
<thead>
<th>Part number</th>
<th>Description</th>
<th>Qty shipped</th>
<th>Weight</th>
</thead>
<tbody>
<td>18293</td>
<td>Blah blah blah</td>
<td>72</td>
<td>20Kg</td>
</tbody>
</table>
</td>
</tr>
<tr>
<td>2</td>
<td>Thurs</td>
<td>Jonno</td>
<td>£1</td>
</tr>
<tr>
<td>3</td>
<td>Fri</td>
<td>Jonno</td>
<td>£7</td>
</tr>
</table>
Here's a fiddle: http://jsfiddle.net/yuW7f/ - The problem here is that the row containing the inner table, is totally unrelated to the order row
If you are looking for a parent element you can use to group related rows, you can use <tbody> elements. A table can have multiple <tbody> elements:
<table>
<thead>
<tr>
<th>Package number</th>
<th>Date placed</th>
<th>Placed by</th>
<th>Total cost</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Weds</td>
<td>Jonno</td>
<td>£15</td>
</tr>
<tr>
<td colspan="4">
<table>
<thead>
<tr>
<th>Part number</th>
<th>Description</th>
<th>Qty shipped</th>
<th>Weight</th>
</tr>
</thead>
<tbody>
<tr>
<td>18293</td>
<td>Blah blah blah</td>
<td>72</td>
<td>20Kg</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
<tr>
<td>2</td>
<td>Thurs</td>
<td>Jonno</td>
<td>£1</td>
</tr>
<tr>
<td>3</td>
<td>Fri</td>
<td>Jonno</td>
<td>£7</td>
</tr>
</table>
Whether or not that makes your code more semantically correct is debatable. You could also give your rows classes to indicate whether the row is a summary row or a detail row, or attributes to indicate relationships to other rows. Semantically, it seems fine as it is to me.
By the way, you are missing some <tr> elements. A <tbody>, <thead>, or <tfoot> element does not replace a <tr> element.
I'm having trouble getting a link clicked on Selenium IDE.
The id is picked from the itteration in a while loop but i can't get the ID echo'ed in somehow.
This is my code:
<tr>
<td>open</td>
<td>/list</td>
<td></td>
</tr>
<tr>
<td>storeAllLinks</td>
<td>linkArray</td>
<td></td>
</tr>
<tr>
<td>getEval</td>
<td>0</td>
<td>index</td>
</tr>
<tr>
<td>while</td>
<td>index < 10;</td><!-- should be array length but that wont word eighter, problem for later -->
<td></td>
</tr>
<tr>
<td>clickAndWait</td>
<td>id=${index}</td>
<td></td>
</tr>
<tr>
<td>getEval</td>
<td>index++;</td>
<td></td>
</tr>
<tr>
<td>pause</td>
<td>1000</td>
<td></td>
</tr>
<tr>
<td>endWhile</td>
<td></td>
<td></td>
</tr>
Anny clues on whats going wrong would be great help.
Thank you in advance!
You can locate link by its text. Use of Xpath and link text might help you.
Or please share part of HTML.
I'm trying to use Selenium IDE to test a web app. On one page, there are several links which trigger modal windows. I'd like to test ALL the links on the page to ensure that ALL cause modals to pop up.
Each link has a class "modal" so I thought I could just change the target value in IDE css=a.modal but that finds only the first link.
I've found a few solutions but they use Selenium RC with Java/Python and those can't be directly translated to the IDE.
I understand the IDE is not as powerful but I'm setting this up for a non-programmer to use and up till now, it's been really simple for them.
i had to read up a lot and i want to thank #ohaal and #aleh for their inputs. i used their suggested links as well as some more external reading to reach the solution like so:
<tr>
<td>open</td>
<td>/logout</td>
<td></td>
</tr>
<tr>
<td>clickAndWait</td>
<td>link=Forum</td>
<td></td>
</tr>
<tr>
<td>storeXpathCount</td>
<td>//p[3]/span/a[contains(#class, 'modal')]</td>
<td>users</td>
</tr>
<tr>
<td>store</td>
<td>1</td>
<td>i</td>
</tr>
<tr>
<td>while</td>
<td>storedVars.i<=storedVars.users</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>//descendant::a[contains(#class, 'modal')][${i}]</td>
<td></td>
</tr>
<tr>
<td>waitForText</td>
<td>css=h2</td>
<td>You are not logged in</td>
</tr>
<tr>
<td>click</td>
<td>css=#cross_button > a > img</td>
<td></td>
</tr>
<tr>
<td>store</td>
<td>javascript{storedVars.i++}</td>
<td></td>
</tr>
<tr>
<td>endWhile</td>
<td></td>
<td></td>
</tr>
i'm not selecting any one answer as all of them contributed to the final solution.
Use a while loop, and this XPath expression as a locator: //a[contains(#class, 'modal')]
//a[contains(#class, 'modal')][1], //a[contains(#class, 'modal')][2] and so on should point to the links you're after.
See this link for more information on how to do while loops in Selenium IDE if you are unfamiliar with it.
Try this (you have to download this extension):
<tr>
<td>storeCssCount</td>
<td>css=a.modal</td>
<td>links</td>
</tr>
<tr>
<td>store</td>
<td>0</td>
<td>i</td>
</tr>
<tr>
<td>label</td>
<td>l1</td>
<td></td>
</tr>
<tr>
<td>getEval</td>
<td>storedVars.i++</td>
<td></td>
</tr>
<tr>
<td>click</td>
<td>//descendant::a[contains(#class, 'modal')][${i}]</td>
<td></td>
</tr>
<tr>
<td>gotoIf</td>
<td>--storedVars.links</td>
<td>l1</td>
</tr>
Insert your assertions and probably pop-ups closings before last command (gotoIf).