1 /*
2 * Copyright (c) 2002-2025 Gargoyle Software Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 * https://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 package org.htmlunit;
16
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.List;
20
21 import org.htmlunit.html.DomElement;
22 import org.htmlunit.html.HtmlAnchor;
23 import org.htmlunit.html.HtmlElement;
24 import org.htmlunit.html.HtmlInput;
25 import org.htmlunit.html.HtmlPage;
26
27 /**
28 * Utility class which contains standard assertions for HTML pages.
29 *
30 * <p>This class provides a collection of static assertion methods for testing
31 * HTML page content, structure, and behavior. All assertion methods throw
32 * {@link AssertionError} when the expected condition is not met.</p>
33 *
34 * <p>Common use cases include:</p>
35 * <ul>
36 * <li>Verifying page titles and content</li>
37 * <li>Checking for presence/absence of elements</li>
38 * <li>Validating form inputs and links</li>
39 * <li>Ensuring accessibility attributes are properly set</li>
40 * </ul>
41 *
42 * @author Daniel Gredler
43 * @author Mike Bowler
44 * @author Ahmed Ashour
45 * @author Ronald Brill
46 */
47 public final class WebAssert {
48
49 /**
50 * Private to prevent instantiation.
51 */
52 private WebAssert() {
53 // Empty.
54 }
55
56 /**
57 * Verifies that the specified page's title equals the specified expected title.
58 *
59 * @param page the page to check
60 * @param title the expected title
61 * @throws AssertionError if the page title does not match the expected title
62 * @throws NullPointerException if page or title is null
63 */
64 public static void assertTitleEquals(final HtmlPage page, final String title) {
65 final String s = page.getTitleText();
66 if (!title.equals(s)) {
67 final String msg = "Page title '" + s + "' does not match expected title '" + title + "'.";
68 throw new AssertionError(msg);
69 }
70 }
71
72 /**
73 * Verifies that the specified page's title contains the specified substring.
74 *
75 * @param page the page to check
76 * @param titlePortion the substring which the page title is expected to contain
77 * @throws AssertionError if the page title does not contain the substring
78 * @throws NullPointerException if page or titlePortion is null
79 */
80 public static void assertTitleContains(final HtmlPage page, final String titlePortion) {
81 final String s = page.getTitleText();
82 if (!s.contains(titlePortion)) {
83 final String msg = "Page title '" + s + "' does not contain the expected substring '" + titlePortion + "'.";
84 throw new AssertionError(msg);
85 }
86 }
87
88 /**
89 * Verifies that the specified page's title matches the specified regular expression.
90 *
91 * @param page the page to check
92 * @param regex the regular expression that the page title is expected to match
93 * @throws AssertionError if the page title does not match the regular expression
94 * @throws NullPointerException if page or regex is null
95 */
96 public static void assertTitleMatches(final HtmlPage page, final String regex) {
97 final String s = page.getTitleText();
98 if (!s.matches(regex)) {
99 final String msg = "Page title '" + s + "' does not match the expected regular expression '" + regex + "'.";
100 throw new AssertionError(msg);
101 }
102 }
103
104 /**
105 * Verifies that the specified page contains an element with the specified ID.
106 *
107 * @param page the page to check
108 * @param id the ID of an element expected in the page
109 * @throws AssertionError if no element with the specified ID is found
110 * @throws NullPointerException if page or id is null
111 */
112 public static void assertElementPresent(final HtmlPage page, final String id) {
113 try {
114 page.getHtmlElementById(id);
115 }
116 catch (final ElementNotFoundException e) {
117 final String msg = "Expected element with ID '" + id + "' was not found on the page.";
118 throw new AssertionError(msg, e);
119 }
120 }
121
122 /**
123 * Verifies that the specified page contains an element matching the specified XPath expression.
124 *
125 * <p><b>Example usage:</b></p>
126 * <pre>{@code
127 * WebAssert.assertElementPresentByXPath(page, "//div[@class='error']");
128 * WebAssert.assertElementPresentByXPath(page, "//input[@type='submit' and @value='Login']");
129 * }</pre>
130 *
131 * @param page the page to check
132 * @param xpath the XPath expression which is expected to match an element in the page
133 * @throws AssertionError if no elements match the XPath expression
134 * @throws NullPointerException if page or xpath is null
135 */
136 public static void assertElementPresentByXPath(final HtmlPage page, final String xpath) {
137 final List<?> elements = page.getByXPath(xpath);
138 if (elements.isEmpty()) {
139 final String msg = "No elements found matching the XPath expression '" + xpath + "'.";
140 throw new AssertionError(msg);
141 }
142 }
143
144 /**
145 * Verifies that the specified page does not contain an element with the specified ID.
146 *
147 * @param page the page to check
148 * @param id the ID of an element which is expected to not exist on the page
149 * @throws AssertionError if an element with the specified ID is found
150 * @throws NullPointerException if page or id is null
151 */
152 public static void assertElementNotPresent(final HtmlPage page, final String id) {
153 try {
154 page.getHtmlElementById(id);
155 }
156 catch (final ElementNotFoundException e) {
157 return;
158 }
159 final String msg = "Found unexpected element with ID '" + id + "' on the page.";
160 throw new AssertionError(msg);
161 }
162
163 /**
164 * Verifies that the specified page does not contain an element matching the specified XPath
165 * expression.
166 *
167 * @param page the page to check
168 * @param xpath the XPath expression which is expected to not match any element in the page
169 * @throws AssertionError if any elements match the XPath expression
170 */
171 public static void assertElementNotPresentByXPath(final HtmlPage page, final String xpath) {
172 final List<?> elements = page.getByXPath(xpath);
173 if (!elements.isEmpty()) {
174 final String msg = "Found " + elements.size()
175 + " unexpected element(s) matching the XPath expression '"
176 + xpath + "'.";
177 throw new AssertionError(msg);
178 }
179 }
180
181 /**
182 * Verifies that the specified page contains the specified text.
183 *
184 * @param page the page to check
185 * @param text the text to check for
186 * @throws AssertionError if the page does not contain the specified text
187 * @throws NullPointerException if page or text is null
188 */
189 public static void assertTextPresent(final HtmlPage page, final String text) {
190 if (!page.asNormalizedText().contains(text)) {
191 final String msg = "Expected text '" + text + "' was not found on the page.";
192 throw new AssertionError(msg);
193 }
194 }
195
196 /**
197 * Verifies that the element on the specified page which matches the specified ID contains the
198 * specified text.
199 *
200 * @param page the page to check
201 * @param text the text to check for
202 * @param id the ID of the element which is expected to contain the specified text
203 * @throws AssertionError if the element does not contain the specified text
204 * @throws ElementNotFoundException if no element with the specified ID exists
205 * @throws NullPointerException if any parameter is null
206 */
207 public static void assertTextPresentInElement(final HtmlPage page, final String text, final String id) {
208 try {
209 final HtmlElement element = page.getHtmlElementById(id);
210 if (!element.asNormalizedText().contains(text)) {
211 final String msg = "Element with ID '" + id + "' does not contain the expected text '" + text + "'.";
212 throw new AssertionError(msg);
213 }
214 }
215 catch (final ElementNotFoundException e) {
216 final String msg = "Cannot verify text content: element with ID '" + id + "' was not found on the page.";
217 throw new AssertionError(msg, e);
218 }
219 }
220
221 /**
222 * Verifies that the specified page does not contain the specified text.
223 *
224 * @param page the page to check
225 * @param text the text to check for
226 * @throws AssertionError if the page contains the specified text
227 * @throws NullPointerException if page or text is null
228 */
229 public static void assertTextNotPresent(final HtmlPage page, final String text) {
230 if (page.asNormalizedText().contains(text)) {
231 final String msg = "Found unexpected text '" + text + "' on the page.";
232 throw new AssertionError(msg);
233 }
234 }
235
236 /**
237 * Verifies that the element on the specified page which matches the specified ID does not
238 * contain the specified text.
239 *
240 * @param page the page to check
241 * @param text the text to check for
242 * @param id the ID of the element which is expected to not contain the specified text
243 */
244 public static void assertTextNotPresentInElement(final HtmlPage page, final String text, final String id) {
245 try {
246 final HtmlElement element = page.getHtmlElementById(id);
247 if (element.asNormalizedText().contains(text)) {
248 final String msg = "Element with ID '" + id + "' contains unexpected text '" + text + "'.";
249 throw new AssertionError(msg);
250 }
251 }
252 catch (final ElementNotFoundException e) {
253 final String msg = "Cannot verify text content: element with ID '" + id + "' was not found on the page.";
254 throw new AssertionError(msg);
255 }
256 }
257
258 /**
259 * Verifies that the specified page contains a link with the specified ID.
260 *
261 * @param page the page to check
262 * @param id the ID of the link which the page is expected to contain
263 * @throws AssertionError if no link with the specified ID is found
264 * @see #assertLinkNotPresent(HtmlPage, String)
265 * @see #assertLinkPresentWithText(HtmlPage, String)
266 */
267 public static void assertLinkPresent(final HtmlPage page, final String id) {
268 try {
269 page.getDocumentElement().getOneHtmlElementByAttribute("a", DomElement.ID_ATTRIBUTE, id);
270 }
271 catch (final ElementNotFoundException e) {
272 final String msg = "Expected link with ID '" + id + "' was not found on the page.";
273 throw new AssertionError(msg, e);
274 }
275 }
276
277 /**
278 * Verifies that the specified page does not contain a link with the specified ID.
279 *
280 * @param page the page to check
281 * @param id the ID of the link which the page is expected to not contain
282 * @throws AssertionError if a link with the specified ID is found
283 * @see #assertLinkPresent(HtmlPage, String)
284 * @see #assertLinkNotPresentWithText(HtmlPage, String)
285 */
286 public static void assertLinkNotPresent(final HtmlPage page, final String id) {
287 try {
288 page.getDocumentElement().getOneHtmlElementByAttribute("a", DomElement.ID_ATTRIBUTE, id);
289 final String msg = "Found unexpected link with ID '" + id + "' on the page.";
290 throw new AssertionError(msg);
291 }
292 catch (final ElementNotFoundException expected) {
293 // Expected behavior - link should not be present
294 }
295 }
296
297 /**
298 * Verifies that the specified page contains a link with the specified text. The specified text
299 * may be a substring of the entire text contained by the link.
300 *
301 * @param page the page to check
302 * @param text the text which a link in the specified page is expected to contain
303 */
304 public static void assertLinkPresentWithText(final HtmlPage page, final String text) {
305 boolean found = false;
306 for (final HtmlAnchor a : page.getAnchors()) {
307 if (a.asNormalizedText().contains(text)) {
308 found = true;
309 break;
310 }
311 }
312 if (!found) {
313 final String msg = "Expected link containing text '" + text + "' was not found on the page.";
314 throw new AssertionError(msg);
315 }
316 }
317
318 /**
319 * Verifies that the specified page does not contain a link with the specified text. The
320 * specified text may be a substring of the entire text contained by the link.
321 *
322 * @param page the page to check
323 * @param text the text which a link in the specified page is not expected to contain
324 */
325 public static void assertLinkNotPresentWithText(final HtmlPage page, final String text) {
326 boolean found = false;
327 for (final HtmlAnchor a : page.getAnchors()) {
328 if (a.asNormalizedText().contains(text)) {
329 found = true;
330 break;
331 }
332 }
333 if (found) {
334 final String msg = "Found unexpected link containing text '" + text + "' on the page.";
335 throw new AssertionError(msg);
336 }
337 }
338
339 /**
340 * Verifies that the specified page contains a form with the specified name.
341 *
342 * @param page the page to check
343 * @param name the expected name of a form on the page
344 * @throws AssertionError if no form with the specified name is found
345 * @see #assertFormNotPresent(HtmlPage, String)
346 */
347 public static void assertFormPresent(final HtmlPage page, final String name) {
348 try {
349 page.getFormByName(name);
350 }
351 catch (final ElementNotFoundException e) {
352 final String msg = "Expected form with name '" + name + "' was not found on the page.";
353 throw new AssertionError(msg, e);
354 }
355 }
356
357 /**
358 * Verifies that the specified page does not contain a form with the specified name.
359 *
360 * @param page the page to check
361 * @param name the name of a form which should not exist on the page
362 * @throws AssertionError if a form with the specified name is found
363 * @see #assertFormPresent(HtmlPage, String)
364 */
365 public static void assertFormNotPresent(final HtmlPage page, final String name) {
366 try {
367 page.getFormByName(name);
368 }
369 catch (final ElementNotFoundException e) {
370 return;
371 }
372 final String msg = "Found unexpected form with name '" + name + "' on the page.";
373 throw new AssertionError(msg);
374 }
375
376 /**
377 * Verifies that the specified page contains an input element with the specified name.
378 *
379 * @param page the page to check
380 * @param name the name of the input element to look for
381 * @throws AssertionError if no input element with the specified name is found
382 * @see #assertInputNotPresent(HtmlPage, String)
383 * @see #assertInputContainsValue(HtmlPage, String, String)
384 */
385 public static void assertInputPresent(final HtmlPage page, final String name) {
386 final String xpath = "//input[@name='" + name + "']";
387 final List<?> list = page.getByXPath(xpath);
388 if (list.isEmpty()) {
389 throw new AssertionError("Expected input element with name '" + name + "' was not found on the page.");
390 }
391 }
392
393 /**
394 * Verifies that the specified page does not contain an input element with the specified name.
395 *
396 * @param page the page to check
397 * @param name the name of the input element to look for
398 * @throws AssertionError if an input element with the specified name is found
399 * @throws NullPointerException if page or name is null
400 */
401 public static void assertInputNotPresent(final HtmlPage page, final String name) {
402 final String xpath = "//input[@name='" + name + "']";
403 final List<?> list = page.getByXPath(xpath);
404 if (!list.isEmpty()) {
405 throw new AssertionError("Found unexpected input element with name '" + name + "' on the page.");
406 }
407 }
408
409 /**
410 * Verifies that the input element with the specified name on the specified page contains the
411 * specified value.
412 *
413 * @param page the page to check
414 * @param name the name of the input element to check
415 * @param value the value to check for
416 */
417 public static void assertInputContainsValue(final HtmlPage page, final String name, final String value) {
418 final String xpath = "//input[@name='" + name + "']";
419 final List<?> list = page.getByXPath(xpath);
420 if (list.isEmpty()) {
421 throw new AssertionError("Expected input element with name '" + name + "' was not found on the page.");
422 }
423 final HtmlInput input = (HtmlInput) list.get(0);
424 final String s = input.getValue();
425 if (!s.equals(value)) {
426 throw new AssertionError("Input element '" + name + "' has value '" + s
427 + "' but expected '" + value + "'.");
428 }
429 }
430
431 /**
432 * Verifies that the input element with the specified name on the specified page does not
433 * contain the specified value.
434 *
435 * @param page the page to check
436 * @param name the name of the input element to check
437 * @param value the value to check for
438 */
439 public static void assertInputDoesNotContainValue(final HtmlPage page, final String name, final String value) {
440 final String xpath = "//input[@name='" + name + "']";
441 final List<?> list = page.getByXPath(xpath);
442 if (list.isEmpty()) {
443 throw new AssertionError("Expected input element with name '" + name + "' was not found on the page.");
444 }
445 final HtmlInput input = (HtmlInput) list.get(0);
446 final String s = input.getValue();
447 if (s.equals(value)) {
448 throw new AssertionError("Input element '" + name + "' has unexpected value '" + s + "'.");
449 }
450 }
451
452 /**
453 * <p>Many HTML elements are "tabbable" and can have a <code>tabindex</code> attribute
454 * that determines the order in which the components are navigated when
455 * pressing the tab key. To ensure good usability for keyboard navigation,
456 * all tabbable elements should have the <code>tabindex</code> attribute set.</p>
457 *
458 * <p>This method verifies that all tabbable elements have a valid value set for
459 * the <code>tabindex</code> attribute. Valid values are positive integers,
460 * 0 (for default tab order), or -1 (to exclude from tab order).</p>
461 *
462 * <p>The following elements are checked: a, area, button, input, object, select, textarea</p>
463 *
464 * @param page the page to check
465 * @throws AssertionError if any tabbable element has an invalid or missing tabindex attribute
466 */
467 public static void assertAllTabIndexAttributesSet(final HtmlPage page) {
468 final List<String> tags =
469 Arrays.asList("a", "area", "button", "input", "object", "select", "textarea");
470
471 for (final String tag : tags) {
472 for (final HtmlElement element : page.getDocumentElement().getStaticElementsByTagName(tag)) {
473 final Short tabIndex = element.getTabIndex();
474 if (tabIndex == null || HtmlElement.TAB_INDEX_OUT_OF_BOUNDS.equals(tabIndex)) {
475 final String s = element.getAttributeDirect("tabindex");
476 throw new AssertionError("Invalid tabindex value '" + s + "' found on element.");
477 }
478 }
479 }
480 }
481
482 /**
483 * Many HTML components can have an <code>accesskey</code> attribute which defines a hot key for
484 * keyboard navigation. This method verifies that all the <code>accesskey</code> attributes on the
485 * specified page are unique.
486 *
487 * <p>Duplicate access keys can confuse users and make keyboard navigation unpredictable.</p>
488 *
489 * @param page the page to check
490 * @throws AssertionError if any access key is used more than once on the page
491 */
492 public static void assertAllAccessKeyAttributesUnique(final HtmlPage page) {
493 final List<String> list = new ArrayList<>();
494 for (final HtmlElement element : page.getHtmlElementDescendants()) {
495 final String key = element.getAttributeDirect("accesskey");
496 if (key != null && !key.isEmpty()) {
497 if (list.contains(key)) {
498 throw new AssertionError("Duplicate access key '" + key + "' found on the page.");
499 }
500 list.add(key);
501 }
502 }
503 }
504
505 /**
506 * Verifies that all element IDs in the specified page are unique.
507 *
508 * @param page the page to check
509 * @throws AssertionError if any element ID is used more than once on the page
510 * @throws NullPointerException if page is null
511 */
512 public static void assertAllIdAttributesUnique(final HtmlPage page) {
513 final List<String> list = new ArrayList<>();
514 for (final HtmlElement element : page.getHtmlElementDescendants()) {
515 final String id = element.getId();
516 if (id != null && !id.isEmpty()) {
517 if (list.contains(id)) {
518 throw new AssertionError("Duplicate element ID '" + id + "' found on the page.");
519 }
520 list.add(id);
521 }
522 }
523 }
524
525 /**
526 * Assert that the specified parameter is not null. Throw a NullPointerException
527 * if a null is found.
528 *
529 * @param description the description to pass into the NullPointerException
530 * @param object the object to check for null
531 * @throws NullPointerException if the object is null
532 */
533 public static void notNull(final String description, final Object object) {
534 if (object == null) {
535 throw new NullPointerException(description);
536 }
537 }
538 }