From 67c70d2ed4b9a115f7d98e251106adcf84567c96 Mon Sep 17 00:00:00 2001 From: niranjankagri Date: Fri, 30 Oct 2026 11:52:37 +0530 Subject: [PATCH] Add manual test cases and put case IDs in the OrangeHRM test names docs/manual-test-cases.md: the 22 OrangeHRM cases (login TC-101..107, employees TC-108..110, system users TC-111..115, menu/drop-down/keyboard/tabs TC-116..120, login page and login time TC-121..122). Test methods start with tcNNN_ and their descriptions with [TC-NNN]; TestNgFeaturesTest methods carry the ID of the login case they re-check. dependsOnMethods and the class-qualified references in the docs follow the new names. The interview/ technique lessons, Java exercises and the report demo get no ID. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 + docs/avoid-vs-prefer.md | 6 +- docs/common-interview-traps.md | 8 +- docs/interview-index.md | 30 +-- docs/manual-test-cases.md | 225 ++++++++++++++++++ .../selenium/tests/EmployeeTest.java | 12 +- .../automation/selenium/tests/LoginTest.java | 28 +-- .../selenium/tests/SeleniumScenariosTest.java | 20 +- .../selenium/tests/SystemUsersTest.java | 20 +- .../selenium/tests/TestNgFeaturesTest.java | 36 +-- 10 files changed, 307 insertions(+), 80 deletions(-) create mode 100644 docs/manual-test-cases.md diff --git a/README.md b/README.md index dda48f5..958b692 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,8 @@ Used for: OrangeHRM is used where a realistic application flow makes the interview concept easier to demonstrate. +**Test cases:** every OrangeHRM test starts with its case ID (`tc108_employeeCanBeAddedFoundAndDeleted`, description `[TC-108] ...`). The 22 cases (TC-101 to TC-122) are written out for a manual run in [docs/manual-test-cases.md](docs/manual-test-cases.md); the `interview/` technique lessons and Java exercises have no case IDs. + ## 2. The Internet **Purpose:** Focused Selenium WebDriver exercises ([the-internet.herokuapp.com](https://the-internet.herokuapp.com)). diff --git a/docs/avoid-vs-prefer.md b/docs/avoid-vs-prefer.md index aa553d0..3a6fc62 100644 --- a/docs/avoid-vs-prefer.md +++ b/docs/avoid-vs-prefer.md @@ -423,7 +423,7 @@ soft.assertAll(); // reports every failure, ``` **Why:** soft assertions only collect; `assertAll()` is what fails the test. -**In the lab:** `TestNgFeaturesTest.softAssertChecksTheLoginPage`. +**In the lab:** `TestNgFeaturesTest.tc121_softAssertChecksTheLoginPage`. ### Configuration methods under a group filter 🟑 ⭐ @@ -459,7 +459,7 @@ public void startBrowser() { ... } ``` **Why:** `priority` only sorts the methods TestNG schedules together; with `parallel=classes` each class runs on its own thread, so there is no dependable order between classes. Independent tests can run anywhere, in any order. -**In the lab:** `EmployeeTest.employeeCanBeAddedFoundAndDeleted` (add, find and delete in one test). +**In the lab:** `EmployeeTest.tc108_employeeCanBeAddedFoundAndDeleted` (add, find and delete in one test). ### Retrying every failure πŸ”΄ ⭐ @@ -476,7 +476,7 @@ public void adminLoginIsRetriedOnceOnFailure() { ... } ``` **Why:** blanket retries hide real defects and flaky tests; a targeted retry, visible in the report, does not. -**In the lab:** [`RetryOnce`](../src/test/java/com/automation/selenium/base/RetryOnce.java), `TestNgFeaturesTest.adminLoginIsRetriedOnceOnFailure`; the practice site's known hiccups are handled (and logged) in `PracticeSiteTest.open`. +**In the lab:** [`RetryOnce`](../src/test/java/com/automation/selenium/base/RetryOnce.java), `TestNgFeaturesTest.tc101_adminLoginIsRetriedOnceOnFailure`; the practice site's known hiccups are handled (and logged) in `PracticeSiteTest.open`. ### Shared state in a parallel data provider πŸ”΄ diff --git a/docs/common-interview-traps.md b/docs/common-interview-traps.md index 9f5c0b8..2596dae 100644 --- a/docs/common-interview-traps.md +++ b/docs/common-interview-traps.md @@ -70,7 +70,7 @@ Paths: `interview/` = [`src/test/java/com/automation/selenium/interview/`](../sr ### Can `Select` handle every drop-down? 🟒 ⭐ **Answer:** Only real `` drop-down? | 🟒 ⭐ | `new Select(element)`: `selectByVisibleText`, `selectByValue`, `selectByIndex`, `getOptions`, `getFirstSelectedOption`, `isMultiple`. A disabled option is refused. | [`interview/dropdowns/SelectDropdownTest`](../src/test/java/com/automation/selenium/interview/dropdowns/SelectDropdownTest.java) | Q29 | -| 31 | How do you handle a drop-down that is not a ``. Click the box to open the list, then click the option by its text. | `BasePage.choose()`, `options()` Β· `SeleniumScenariosTest.userRoleDropdownOffersAdminAndEss()` | Q30, Q93 | +| 31 | How do you handle a drop-down that is not a ``. Click the box to open the list, then click the option by its text. | `BasePage.choose()`, `options()` Β· `SeleniumScenariosTest.tc118_userRoleDropdownOffersAdminAndEss()` | Q30, Q93 | | 32 | How do you handle checkboxes and radio buttons? | 🟒 | Read `isSelected()` first and click only if the state must change; a click toggles a checkbox. A selected radio button is never unselected by clicking it. | [`interview/elements/CheckboxTest`](../src/test/java/com/automation/selenium/interview/elements/CheckboxTest.java) | Q39 | | 33 | How do you read a web table? | 🟑 ⭐ | Read the headers, then each row's cells, and map them by position into *column β†’ value*; then any question is plain Java. Prefer stable class names over positions when the cells have them. | [`interview/elements/WebTableTest`](../src/test/java/com/automation/selenium/interview/elements/WebTableTest.java) Β· `BasePage.tableRows()` | Q19 | | 34 | How do you find a table row by its content and click something in it? | 🟑 ⭐ | Find the row by a cell's text, then search *inside the row*: `row.findElement(By.xpath(".//a"))`. Without the dot, `//` searches the whole page. | `WebTableTest.xpathInsideARowMustStartWithADot` Β· `EmployeeListPage.delete()` | Q19 | @@ -110,7 +110,7 @@ All in [`interview/exceptions/SeleniumExceptionsTest`](../src/test/java/com/auto | 38 | `clear()` does not empty a field. Why, and what do you do? | 🟑 | Frameworks such as Vue or React keep their own model and may not see `clear()`. Send real keys instead: Ctrl+A, then Delete. | [`BasePage.type()`](../src/main/java/com/automation/selenium/pages/BasePage.java) | Q36 | | 39 | How do you take a screenshot of the page and of one element? | 🟒 ⭐ | Page: cast the driver to `TakesScreenshot`. Element (Selenium 4): `element.getScreenshotAs(…)`. On failure, take it *before* cleanup. | `BaseTest.attachScreenshot()` Β· `LoginPage.formScreenshot()` Β· `SeleniumScenariosTest.loginFormScreenshotIsAPng()` | Q37, Q97 | | 40 | What is the difference between text, attribute and property? | 🟑 | `getText()` = visible text; `getDomAttribute()` = as written in the HTML; `getDomProperty()` = the live value (typed text, absolute `href`). | `BasePage.valueOf()` Β· `LoginPage.placeholder()` Β· `AppPage.menuLinks()` | Q39 | -| 41 | How do you find broken links? | 🟑 ⭐ | Collect every `href` with Selenium, then request each URL with an HTTP client; Selenium cannot read status codes. 400 or more is broken. | [`Links`](../src/main/java/com/automation/selenium/utils/Links.java) Β· `SeleniumScenariosTest.sideMenuHasNoBrokenLinks()` | Q92 | +| 41 | How do you find broken links? | 🟑 ⭐ | Collect every `href` with Selenium, then request each URL with an HTTP client; Selenium cannot read status codes. 400 or more is broken. | [`Links`](../src/main/java/com/automation/selenium/utils/Links.java) Β· `SeleniumScenariosTest.tc117_sideMenuHasNoBrokenLinks()` | Q92 | | 42 | How do you work with cookies? Can you skip the login? | 🟑 | `driver.manage()`: `getCookies`, `getCookieNamed`, `addCookie`, `deleteCookieNamed`, `deleteAllCookies` (open a page of the site first). Saving the session cookie and adding it to a fresh session skips the login form. | [`interview/cookies/CookieTest`](../src/test/java/com/automation/selenium/interview/cookies/CookieTest.java) | β€” | | 43 | How do you upload a file? | 🟒 ⭐ | `sendKeys(absolutePath)` on the ``; never click it (OS dialog). On Grid add a `LocalFileDetector`. No AutoIt or Robot. | [`interview/upload/FileUploadTest`](../src/test/java/com/automation/selenium/interview/upload/FileUploadTest.java) | Q38 | @@ -121,13 +121,13 @@ All in [`interview/exceptions/SeleniumExceptionsTest`](../src/test/java/com/auto | 44 | What is the Page Object Model, and why use it? | 🟒 ⭐ | One class per page holds its locators and actions; tests only call methods and assert. When the UI changes, you fix one page class instead of many tests. | [`pages/`](../src/main/java/com/automation/selenium/pages): the tests in `tests/` contain no locators | Q40 | | 45 | Should page objects contain assertions? | 🟑 | Generally no: pages return values and states, tests decide what is correct. Pages may fail fast when they cannot do their job (e.g. login rejected). | `LoginPage.isDisplayed()` returns a boolean Β· `LoginPage.loginAs()` throws on a rejected login | Q41 | | 46 | What is PageFactory? Is it the same as POM? | 🟑 ⭐ | POM is a design pattern; PageFactory is one way to create the elements (`@FindBy` + `initElements`). Each field is a lazy proxy that finds the element on every use. `@CacheLookup` keeps the first element and goes stale after a re-render. | `BasePage` constructor Β· `LoginPage.cachedUsernameField` Β· `SeleniumScenariosTest.findByFieldSurvivesAReloadButCacheLookupGoesStale()` | Q42 | -| 47 | What is method chaining (a fluent page API)? | 🟒 | Methods return a page object (`this` or the next page), so steps read like a sentence: `openAdmin().filterByRole("Admin").search()`. | [`SystemUsersPage`](../src/main/java/com/automation/selenium/pages/SystemUsersPage.java) Β· `SystemUsersTest.filterByRoleAndStatus()` | Q43 | +| 47 | What is method chaining (a fluent page API)? | 🟒 | Methods return a page object (`this` or the next page), so steps read like a sentence: `openAdmin().filterByRole("Admin").search()`. | [`SystemUsersPage`](../src/main/java/com/automation/selenium/pages/SystemUsersPage.java) Β· `SystemUsersTest.tc113_filterByRoleAndStatus()` | Q43 | | 48 | What goes into a `BasePage`? | 🟑 | What every page needs: the driver, the wait, PageFactory set-up, safe actions (click, type, read) and the app's shared widgets (spinner, toast, tables). | [`BasePage`](../src/main/java/com/automation/selenium/pages/BasePage.java), [`AppPage`](../src/main/java/com/automation/selenium/pages/AppPage.java) | Q44 | | 49 | How do you run Selenium tests in parallel safely? Is WebDriver "thread-safe"? | πŸ”΄ ⭐ | Give every thread its own browser: keep the driver in a `ThreadLocal`, never in a static field. WebDriver itself is still not thread-safe; ThreadLocal makes sure no two threads share one. | [`DriverManager`](../src/main/java/com/automation/selenium/driver/DriverManager.java) Β· [`interview/framework/ThreadLocalDriverTest`](../src/test/java/com/automation/selenium/interview/framework/ThreadLocalDriverTest.java) (two threads, two sessions) | Q45, Q87 | | 50 | How do you manage the driver's life cycle? | πŸ”΄ | Start a fresh browser before each test, quit it after each test (in `finally`), `remove()` the ThreadLocal slot, and quit any leftover before starting a new one. | `BaseTest.startBrowser()` / `stopBrowser()` Β· `DriverManager.start()`, `quit()` Β· `ThreadLocalDriverTest.quitEmptiesTheThreadsSlot` | Q45 | | 51 | How do you manage configuration (URL, browser, timeouts)? | 🟑 | One properties file on the classpath, overridable by `-Dkey=value`, read through one class; a missing key fails at once with its name. | [`Config`](../src/main/java/com/automation/selenium/config/Config.java) Β· [`interview/framework/ConfigOverrideTest`](../src/test/java/com/automation/selenium/interview/framework/ConfigOverrideTest.java) | Q46, Q77 | | 52 | Where do you keep test data? | 🟑 | Out of the test logic: data providers for tables of cases, generators for unique data, config for credentials. | [`TestData`](../src/test/java/com/automation/selenium/data/TestData.java) (`invalidCredentials`, `newEmployee()`) | Q47 | -| 53 | How do you keep tests independent on a shared environment? | πŸ”΄ ⭐ | Each test creates the data it needs with unique values, never relies on existing records or counts, and deletes what it made, even when it fails. | `EmployeeTest.employeeCanBeAddedFoundAndDeleted()` + `cleanUp()` Β· `BaseTest.runCleanUp()` | Q48, Q88 | +| 53 | How do you keep tests independent on a shared environment? | πŸ”΄ ⭐ | Each test creates the data it needs with unique values, never relies on existing records or counts, and deletes what it made, even when it fails. | `EmployeeTest.tc108_employeeCanBeAddedFoundAndDeleted()` + `cleanUp()` Β· `BaseTest.runCleanUp()` | Q48, Q88 | | 54 | How do you log test steps? | 🟑 | Page methods log each user action; the steps are attached to the running test and shown in the report. Mask secrets. | [`Steps.log()`](../src/main/java/com/automation/selenium/utils/Steps.java) Β· `LoginPage.mask()` | Q50 | ## 9. TestNG core @@ -137,25 +137,25 @@ All in [`interview/exceptions/SeleniumExceptionsTest`](../src/test/java/com/auto | 55 | In what order do TestNG annotations run? `@BeforeTest` or `@BeforeMethod`? | 🟒 ⭐ | Suite β†’ Test β†’ Class β†’ Method β†’ @Test β†’ and back out. `@BeforeTest` runs once per `` tag in testng.xml; `@BeforeMethod` before every test method. | [`interview/testng/AnnotationOrderTest`](../src/test/java/com/automation/selenium/interview/testng/AnnotationOrderTest.java) (records and checks the order) Β· `BaseTest` | Q52, Q53 | | 56 | What does `alwaysRun = true` do? | 🟑 ⭐ | Configuration methods don't inherit class groups; under `-Dgroups=…` a method without `alwaysRun` is left out (no browser!). `alwaysRun` also runs teardown after failures. | `BaseTest.startBrowser()`, `stopBrowser()` Β· `AnnotationOrderTest` | Q54 | | 57 | What are groups, and how do you run them? | 🟒 ⭐ | Labels on tests (`groups = "smoke"`), selected with `-Dgroups=…` or ``/`` in testng.xml. A class-level group applies to every method. | Class-level groups on every test class (e.g. `{"interview", "exceptions"}`) Β· `excludedGroups` in [`pom.xml`](../pom.xml) | Q55, Q75 | -| 58 | What do `priority` and `dependsOnMethods` do? What if the first test fails? | 🟒 ⭐ | `priority` sorts methods before they are scheduled (lower first); it is not a dependency, and with parallel classes there is no order between classes. `dependsOnMethods` runs a test only if the other passed; otherwise it is **skipped**, not failed. | `TestNgFeaturesTest.loginPageIsShown()` β†’ `adminLogsInAfterThePageCheck()` Β· `ReportDemoTest.skippedBecauseDependencyFailed()` | Q56–Q58, Q100 | +| 58 | What do `priority` and `dependsOnMethods` do? What if the first test fails? | 🟒 ⭐ | `priority` sorts methods before they are scheduled (lower first); it is not a dependency, and with parallel classes there is no order between classes. `dependsOnMethods` runs a test only if the other passed; otherwise it is **skipped**, not failed. | `TestNgFeaturesTest.tc121_loginPageIsShown()` β†’ `adminLogsInAfterThePageCheck()` Β· `ReportDemoTest.skippedBecauseDependencyFailed()` | Q56–Q58, Q100 | | 59 | What are `dependsOnGroups` and `enabled = false`? | 🟑 | `dependsOnGroups` waits for a whole group to pass; `alwaysRun = true` makes a dependency "soft". `enabled = false` switches a test off: not run, not reported. | [`interview/testng/DependencyTest`](../src/test/java/com/automation/selenium/interview/testng/DependencyTest.java) | Q58 | -| 60 | What does `invocationCount` do? | 🟒 | Runs the same test N times and reports each run, e.g. to catch a check that only sometimes passes. | `TestNgFeaturesTest.wrongPasswordIsRejectedEveryTime()` Β· `ParallelExecutionTest` | Q56, Q101 | -| 61 | What does `timeOut` do? | 🟑 | Fails the test if it runs longer than the limit: a safety net against hangs, not a replacement for waits. | `TestNgFeaturesTest.loginFinishesWithinOneMinute()` | Q56, Q104 | -| 62 | Hard vs soft assertions? | 🟒 ⭐ | A hard assert stops at the first failure; `SoftAssert` collects every failure and reports them together at `assertAll()`. Forget `assertAll()` and the failures are lost. | `TestNgFeaturesTest.softAssertChecksTheLoginPage()` | Q59, Q98 | +| 60 | What does `invocationCount` do? | 🟒 | Runs the same test N times and reports each run, e.g. to catch a check that only sometimes passes. | `TestNgFeaturesTest.tc102_wrongPasswordIsRejectedEveryTime()` Β· `ParallelExecutionTest` | Q56, Q101 | +| 61 | What does `timeOut` do? | 🟑 | Fails the test if it runs longer than the limit: a safety net against hangs, not a replacement for waits. | `TestNgFeaturesTest.tc122_loginFinishesWithinOneMinute()` | Q56, Q104 | +| 62 | Hard vs soft assertions? | 🟒 ⭐ | A hard assert stops at the first failure; `SoftAssert` collects every failure and reports them together at `assertAll()`. Forget `assertAll()` and the failures are lost. | `TestNgFeaturesTest.tc121_softAssertChecksTheLoginPage()` | Q59, Q98 | | 63 | How do you skip a test at run time? | 🟒 | Throw `SkipException` with the reason; the test is reported as skipped, with that message. | `ReportDemoTest.skippedWhenPreconditionIsMissing()` | Q60, Q73 | -| 64 | How do you test that an exception is thrown? | 🟑 | `@Test(expectedExceptions = X.class, expectedExceptionsMessageRegExp = "…")`, or inside a test `assertThrows` / `expectThrows` (which returns the exception to inspect). | `TestNgFeaturesTest.wrongPasswordThrowsTheLoginError()` Β· `SeleniumExceptionsTest` (`assertThrows`, `expectThrows`) | Q61, Q99 | +| 64 | How do you test that an exception is thrown? | 🟑 | `@Test(expectedExceptions = X.class, expectedExceptionsMessageRegExp = "…")`, or inside a test `assertThrows` / `expectThrows` (which returns the exception to inspect). | `TestNgFeaturesTest.tc102_wrongPasswordThrowsTheLoginError()` Β· `SeleniumExceptionsTest` (`assertThrows`, `expectThrows`) | Q61, Q99 | ## 10. TestNG data, parallel runs, listeners and retry | # | Question | Level | Interview answer | Implementation | PDF | |---|---|---|---|---|---| -| 65 | How does a `@DataProvider` work? How do you share one? | 🟑 ⭐ | It returns `Object[][]`; TestNG runs the test once per row with the row as parameters. Share it by putting it in another class and naming it with `dataProviderClass`. | [`TestData.invalidCredentials()`](../src/test/java/com/automation/selenium/data/TestData.java) Β· `LoginTest.loginIsRejectedForInvalidCredentials()` | Q62, Q63 | +| 65 | How does a `@DataProvider` work? How do you share one? | 🟑 ⭐ | It returns `Object[][]`; TestNG runs the test once per row with the row as parameters. Share it by putting it in another class and naming it with `dataProviderClass`. | [`TestData.invalidCredentials()`](../src/test/java/com/automation/selenium/data/TestData.java) Β· `LoginTest.tc102_loginIsRejectedForInvalidCredentials()` | Q62, Q63 | | 66 | What other forms can a data provider take? | πŸ”΄ | A lazy `Iterator`; a provider with a `Method` parameter that serves several tests; `parallel = true` to run rows at the same time (the test must then be thread-safe). | [`interview/testng/DataProviderVariantsTest`](../src/test/java/com/automation/selenium/interview/testng/DataProviderVariantsTest.java) | Q62 | -| 67 | `@DataProvider` or `@Parameters`? | 🟑 | `@Parameters` passes single values from testng.xml (environment-like settings); `@DataProvider` feeds many rows of test data from code. `@Optional` gives a default when the XML has no value. | `TestNgFeaturesTest.titleMatchesTheSuiteParameter()` Β· [`testng.xml`](../testng.xml) | Q64, Q103 | +| 67 | `@DataProvider` or `@Parameters`? | 🟑 | `@Parameters` passes single values from testng.xml (environment-like settings); `@DataProvider` feeds many rows of test data from code. `@Optional` gives a default when the XML has no value. | `TestNgFeaturesTest.tc121_titleMatchesTheSuiteParameter()` Β· [`testng.xml`](../testng.xml) | Q64, Q103 | | 68 | What goes into testng.xml? | 🟒 | Suites, tests, classes or packages, group include/exclude, parameters, listeners and parallel settings. | [`testng.xml`](../testng.xml) | Q65 | | 69 | How do you run tests in parallel? Why `parallel=classes`? | πŸ”΄ ⭐ | Set `parallel` (methods, classes, tests, instances) and `thread-count`; on one method, `invocationCount` + `threadPoolSize`. `classes` keeps each class's methods on one thread, so its fields and `dependsOnMethods` stay safe. | Surefire `parallel`/`threadCount` in [`pom.xml`](../pom.xml) Β· [`interview/testng/ParallelExecutionTest`](../src/test/java/com/automation/selenium/interview/testng/ParallelExecutionTest.java) (threads forced to overlap; ThreadLocal keeps them apart) | Q66, Q67 | | 70 | Which listeners do you know? How do you register one? | πŸ”΄ | `ITestListener`, `ISuiteListener`, `IReporter`, `IRetryAnalyzer`, `IAnnotationTransformer`. Register with `@Listeners` (applies to the whole suite!), in testng.xml, or through ServiceLoader. | [`interview/testng/ListenerTest`](../src/test/java/com/automation/selenium/interview/testng/ListenerTest.java) + `TestEventsListener` Β· `HtmlReportListener` via [`META-INF/services`](../src/main/resources/META-INF/services/org.testng.ITestNGListener) | Q68, Q69 | -| 71 | How do you retry a failed test? Should you retry every failure? | πŸ”΄ ⭐ | Implement `IRetryAnalyzer` and attach it with `retryAnalyzer = …`. Retry only known environment hiccups; retrying everything hides real defects. | [`RetryOnce`](../src/test/java/com/automation/selenium/base/RetryOnce.java) Β· `TestNgFeaturesTest.adminLoginIsRetriedOnceOnFailure()` | Q71, Q102 | +| 71 | How do you retry a failed test? Should you retry every failure? | πŸ”΄ ⭐ | Implement `IRetryAnalyzer` and attach it with `retryAnalyzer = …`. Retry only known environment hiccups; retrying everything hides real defects. | [`RetryOnce`](../src/test/java/com/automation/selenium/base/RetryOnce.java) Β· `TestNgFeaturesTest.tc101_adminLoginIsRetriedOnceOnFailure()` | Q71, Q102 | | 72 | Why does a test show as skipped? | 🟑 | A failed dependency, a `SkipException`, a failed `@Before…` configuration method, or a retried attempt (the earlier try is reported as skipped). | `ReportDemoTest` (both skip types) | Q73 | ## 11. Reporting, build and run diff --git a/docs/manual-test-cases.md b/docs/manual-test-cases.md new file mode 100644 index 0000000..68376bc --- /dev/null +++ b/docs/manual-test-cases.md @@ -0,0 +1,225 @@ +# Manual test cases + +The OrangeHRM test cases this lab automates, written as a tester would run them by hand. Every case has +an ID; the TestNG method that covers it starts with the same ID (`tc108_employeeCanBeAddedFoundAndDeleted`) +and so does its description (`[TC-108] An employee can be added, found by ID and deleted`), so a failure +in the report leads straight to the case below. + +| ID range | Area | Automated in | +|---|---|---| +| TC-101 to TC-107 | Login and logout | `tests/LoginTest` | +| TC-108 to TC-110 | Employees (PIM) | `tests/EmployeeTest` | +| TC-111 to TC-115 | System users (Admin) | `tests/SystemUsersTest` | +| TC-116 to TC-120 | Menu, drop-down, keyboard, tabs | `tests/SeleniumScenariosTest` | +| TC-121 to TC-122 | Login page and login time | `tests/TestNgFeaturesTest` | + +`TestNgFeaturesTest` shows TestNG features (priority, dependsOnMethods, invocationCount, timeOut, +retryAnalyzer, expectedExceptions, @Parameters) on login checks; methods that re-check a login case carry +that case's ID (TC-101, TC-102). + +Not listed, on purpose: the `interview/` lessons (Selenium techniques on practice sites: alerts, frames, +waits, exceptions, locators ...) and `interview/java/` (Java coding exercises): they demonstrate techniques +and code, not the behaviour of an application under test. Neither are three technique scenarios in +`SeleniumScenariosTest` (JavaScript reading the page state, @FindBy vs @CacheLookup, an element screenshot) +or `ReportDemoTest`, which fails and skips on purpose. + +## Setup + +- OrangeHRM demo: (public, shared, reset regularly). +- Login: `Admin` / `admin123`. + +## Summary + +| ID | Title | Type | Priority | +|---|---|---|---| +| TC-101 | Admin logs in and sees the dashboard | Smoke | High | +| TC-102 | Login is rejected for invalid credentials (3 data sets) | Negative | High | +| TC-103 | Username and password are both required | Negative | Medium | +| TC-104 | Password is required when only the username is entered | Negative | Medium | +| TC-105 | Admin logs out and returns to the login page | Smoke | High | +| TC-106 | Back and reload after logout end on the login page | Security | High | +| TC-107 | The dashboard URL redirects to login when not logged in | Security | High | +| TC-108 | An employee can be added, found by ID and deleted | Smoke / E2E | High | +| TC-109 | First and last name are required for a new employee | Negative | Medium | +| TC-110 | Searching an unknown employee ID finds no records | Negative | Low | +| TC-111 | System Users page opens from the Admin menu | Smoke | High | +| TC-112 | Filtering by username lists only that user | Regression | Medium | +| TC-113 | Filtering by role and status lists only matching users | Regression | Medium | +| TC-114 | Searching an unknown username finds no records | Negative | Low | +| TC-115 | Reset clears every filter | Regression | Low | +| TC-116 | The side menu lists the core modules | Regression | Medium | +| TC-117 | The side menu has no broken links | Regression | Medium | +| TC-118 | The User Role drop-down offers Admin and ESS | Regression | Low | +| TC-119 | Admin can log in with the keyboard only | Accessibility | Medium | +| TC-120 | The dashboard opens in a second tab with the same session | Regression | Low | +| TC-121 | The login page shows its title, fields and button | Smoke | Medium | +| TC-122 | Login finishes within one minute | Performance | Low | + +--- + +## Login (`LoginTest`) + +### TC-101 Admin logs in and sees the dashboard + +Also automated by `TestNgFeaturesTest.tc101_adminLogsInAfterThePageCheck` and `tc101_adminLoginIsRetriedOnceOnFailure`. + +| # | Step | Expected result | +|---|---|---| +| 1 | Open the login page, enter `Admin` / `admin123`, click **Login** | The dashboard | +| 2 | Look at the top bar and the page | The logged-in user's name is shown; at least one dashboard widget | + +### TC-102 Login is rejected for invalid credentials + +Also automated by `TestNgFeaturesTest.tc102_wrongPasswordThrowsTheLoginError` and `tc102_wrongPasswordIsRejectedEveryTime` (runs twice). +For every data set: enter the user and password, click **Login**. Expected: "Invalid credentials"; still +on the login page. + +| Data set | Username | Password | +|---|---|---| +| wrong password | `Admin` | `wrong-password` | +| unknown user | `no.such.user` | `admin123` | +| wrong case in user name and password | `admin` | `ADMIN123` | + +### TC-103 Username and password are both required + +| # | Step | Expected result | +|---|---|---| +| 1 | Click **Login** with both fields empty | "Required" under Username and under Password | + +### TC-104 Password is required when only the username is entered + +| # | Step | Expected result | +|---|---|---| +| 1 | Enter `Admin`, leave the password empty, click **Login** | "Required" under Password; still on the login page | + +### TC-105 Admin logs out and returns to the login page + +| # | Step | Expected result | +|---|---|---| +| 1 | Log in; open the user menu (top right), click **Logout** | The login page | + +### TC-106 Back and reload after logout end on the login page + +| # | Step | Expected result | +|---|---|---| +| 1 | Log in, log out (TC-105) | The login page | +| 2 | Press the browser's **Back** button, then reload | Still the login page, not the dashboard | + +### TC-107 The dashboard URL redirects to login when not logged in + +| # | Step | Expected result | +|---|---|---| +| 1 | In a fresh private window open `.../web/index.php/dashboard/index` | Redirect to the login page | + +## Employees (`EmployeeTest`) + +Precondition for all: logged in as Admin. + +### TC-108 An employee can be added, found by ID and deleted + +Test data: a random first and last name and a unique employee ID. + +| # | Step | Expected result | +|---|---|---| +| 1 | **PIM** β€Ί **Add**, enter first name, last name and employee ID, **Save** | The new profile shows the full name and the employee ID | +| 2 | **PIM**, search by the employee ID | Exactly one row with that ID and name | +| 3 | Delete the row, confirm | "Successfully Deleted" | +| 4 | Search the ID again | "No Records Found" | + +### TC-109 First and last name are required for a new employee + +| # | Step | Expected result | +|---|---|---| +| 1 | **PIM** β€Ί **Add**, click **Save** without names | "Required" under first name and under last name; nothing saved | + +### TC-110 Searching an unknown employee ID finds no records + +| # | Step | Expected result | +|---|---|---| +| 1 | **PIM**, search employee ID `NO-SUCH-ID` | "No Records Found"; empty table | + +## System users (`SystemUsersTest`) + +Precondition for all: logged in as Admin, **Admin** opened from the side menu. + +### TC-111 System Users page opens from the Admin menu + +| # | Step | Expected result | +|---|---|---| +| 1 | Click **Admin** in the side menu | Module title "Admin"; users are listed | + +### TC-112 Filtering by username lists only that user + +| # | Step | Expected result | +|---|---|---| +| 1 | Username `Admin`, **Search** | At least one row; every username is "Admin" | + +### TC-113 Filtering by role and status lists only matching users + +| # | Step | Expected result | +|---|---|---| +| 1 | User Role **Admin**, Status **Enabled**, **Search** | At least one row; every row has role Admin and status Enabled | + +### TC-114 Searching an unknown username finds no records + +| # | Step | Expected result | +|---|---|---| +| 1 | Username `no.such.user.42`, **Search** | "No Records Found"; empty table | + +### TC-115 Reset clears every filter + +| # | Step | Expected result | +|---|---|---| +| 1 | Username `Admin`, User Role **Admin**, Status **Enabled**, then **Reset** | Username empty; User Role and Status show "-- Select --" | + +## Menu, drop-down, keyboard, tabs (`SeleniumScenariosTest`) + +Precondition for TC-116 to TC-118 and TC-120: logged in as Admin. + +### TC-116 The side menu lists the core modules + +| # | Step | Expected result | +|---|---|---| +| 1 | Read the side menu entries | They include Admin, PIM, Time, Dashboard; no entry is blank | + +### TC-117 The side menu has no broken links + +| # | Step | Expected result | +|---|---|---| +| 1 | Open every side menu link (or check its address with an HTTP client) | Every link answers without an error status (no 4xx/5xx) | + +### TC-118 The User Role drop-down offers Admin and ESS + +| # | Step | Expected result | +|---|---|---| +| 1 | **Admin**, open the **User Role** filter | Options exactly: "-- Select --", "Admin", "ESS" | + +### TC-119 Admin can log in with the keyboard only + +| # | Step | Expected result | +|---|---|---| +| 1 | On the login page type `Admin`, press Tab, type `admin123`, press Enter | The dashboard (no mouse needed) | + +### TC-120 The dashboard opens in a second tab with the same session + +| # | Step | Expected result | +|---|---|---| +| 1 | Open a new tab, go to the dashboard URL | The dashboard, without logging in again; two tabs open | +| 2 | Close the new tab, return to the first | Only the first tab is left, still on the dashboard | + +## Login page and login time (`TestNgFeaturesTest`) + +### TC-121 The login page shows its title, fields and button + +Automated by `tc121_softAssertChecksTheLoginPage` (every check, reported together), `tc121_loginPageIsShown` and `tc121_titleMatchesTheSuiteParameter`. + +| # | Step | Expected result | +|---|---|---| +| 1 | Open the login page | Page title "OrangeHRM"; the login form is shown | +| 2 | Look at the fields and the button | Placeholders "Username" and "Password"; button "Login". Note **every** deviation before failing the case | + +### TC-122 Login finishes within one minute + +| # | Step | Expected result | +|---|---|---| +| 1 | Log in as Admin with a stopwatch | The dashboard within 60 seconds | diff --git a/src/test/java/com/automation/selenium/tests/EmployeeTest.java b/src/test/java/com/automation/selenium/tests/EmployeeTest.java index 78ecd94..fcd625c 100644 --- a/src/test/java/com/automation/selenium/tests/EmployeeTest.java +++ b/src/test/java/com/automation/selenium/tests/EmployeeTest.java @@ -44,8 +44,8 @@ public class EmployeeTest extends BaseTest { * employee by ID in the list, deletes it and checks it is gone. */ // Also in the "smoke" group, besides the class group "pim" - @Test(groups = "smoke", description = "An employee can be added, found by ID and deleted") - public void employeeCanBeAddedFoundAndDeleted() { + @Test(groups = "smoke", description = "[TC-108] An employee can be added, found by ID and deleted") + public void tc108_employeeCanBeAddedFoundAndDeleted() { // A new employee with a random 10-character ID, e.g. QA01234567 Employee employee = TestData.newEmployee(); // Set before saving: a failure right after Save may still have created the employee @@ -82,8 +82,8 @@ public void employeeCanBeAddedFoundAndDeleted() { * Saves the Add Employee form without names and checks "Required" under * first and last name. Nothing is saved, so there is nothing to clean up. */ - @Test(description = "First and last name are required for a new employee") - public void firstAndLastNameAreRequired() { + @Test(description = "[TC-109] First and last name are required for a new employee") + public void tc109_firstAndLastNameAreRequired() { // Open the empty form and press Save without typing anything AddEmployeePage form = loginAsAdmin().openPim().addEmployee().trySave(); @@ -94,8 +94,8 @@ public void firstAndLastNameAreRequired() { /** * Searches for an employee ID that does not exist and checks the empty result. */ - @Test(description = "Searching for an unknown employee ID finds no records") - public void unknownEmployeeIdFindsNoRecords() { + @Test(description = "[TC-110] Searching for an unknown employee ID finds no records") + public void tc110_unknownEmployeeIdFindsNoRecords() { // Search for an ID nobody has EmployeeListPage list = loginAsAdmin().openPim().searchById("NO-SUCH-ID"); diff --git a/src/test/java/com/automation/selenium/tests/LoginTest.java b/src/test/java/com/automation/selenium/tests/LoginTest.java index ee359de..b885a94 100644 --- a/src/test/java/com/automation/selenium/tests/LoginTest.java +++ b/src/test/java/com/automation/selenium/tests/LoginTest.java @@ -31,8 +31,8 @@ public class LoginTest extends BaseTest { * the user name in the top bar and at least one widget. */ // Also in the "smoke" group (the quick check: -Dgroups=smoke) - @Test(groups = "smoke", description = "Admin can log in and sees the dashboard") - public void adminCanLogIn() { + @Test(groups = "smoke", description = "[TC-101] Admin can log in and sees the dashboard") + public void tc101_adminCanLogIn() { // Open the login page, log in with the configured Admin user, wait for the dashboard DashboardPage dashboard = loginAsAdmin(); @@ -55,8 +55,8 @@ public void adminCanLogIn() { // Data-driven: TestNG calls this method once per row of the provider (3 runs). // dataProviderClass is needed because the provider lives in another class. @Test(dataProvider = "invalidCredentials", dataProviderClass = TestData.class, - description = "Login is rejected for invalid credentials") - public void loginIsRejectedForInvalidCredentials(String caseName, String username, String password) { + description = "[TC-102] Login is rejected for invalid credentials") + public void tc102_loginIsRejectedForInvalidCredentials(String caseName, String username, String password) { // Type the row's user name and password and press Login (submit does not expect success) LoginPage login = openLoginPage().submit(username, password); @@ -69,8 +69,8 @@ public void loginIsRejectedForInvalidCredentials(String caseName, String usernam /** * Submits the empty form and checks "Required" under both fields. */ - @Test(description = "Username and password are both required") - public void usernameAndPasswordAreRequired() { + @Test(description = "[TC-103] Username and password are both required") + public void tc103_usernameAndPasswordAreRequired() { // Press Login with both fields empty LoginPage login = openLoginPage().submit("", ""); @@ -82,8 +82,8 @@ public void usernameAndPasswordAreRequired() { /** * Submits only a user name and checks "Required" under the Password field. */ - @Test(description = "Password is required when only the username is entered") - public void passwordIsRequired() { + @Test(description = "[TC-104] Password is required when only the username is entered") + public void tc104_passwordIsRequired() { // User name filled in, password left empty LoginPage login = openLoginPage().submit("Admin", ""); @@ -96,8 +96,8 @@ public void passwordIsRequired() { /** * Logs in, logs out through the user menu and checks the login page is shown. */ - @Test(groups = "smoke", description = "Admin can log out and returns to the login page") - public void adminCanLogOut() { + @Test(groups = "smoke", description = "[TC-105] Admin can log out and returns to the login page") + public void tc105_adminCanLogOut() { // Log in, then user menu (top right) β†’ Logout LoginPage login = loginAsAdmin().logout(); @@ -111,8 +111,8 @@ public void adminCanLogOut() { * cache); the reload asks the server again, and with the session ended it * must answer with the login page. */ - @Test(description = "Back and reload after logout end on the login page") - public void backAfterLogoutStaysLoggedOut() { + @Test(description = "[TC-106] Back and reload after logout end on the login page") + public void tc106_backAfterLogoutStaysLoggedOut() { // Log in and out LoginPage login = loginAsAdmin().logout(); // Make sure the logout itself worked before testing Back @@ -132,8 +132,8 @@ public void backAfterLogoutStaysLoggedOut() { * Opens the dashboard URL in a browser that never logged in. OrangeHRM * must redirect to the login page instead of showing the dashboard. */ - @Test(description = "The dashboard URL redirects to login when not logged in") - public void dashboardRequiresLogin() { + @Test(description = "[TC-107] The dashboard URL redirects to login when not logged in") + public void tc107_dashboardRequiresLogin() { // Go straight to the dashboard URL (visit() does not wait for the dashboard) new DashboardPage(driver()).visit(); diff --git a/src/test/java/com/automation/selenium/tests/SeleniumScenariosTest.java b/src/test/java/com/automation/selenium/tests/SeleniumScenariosTest.java index ff296f8..0e3103e 100644 --- a/src/test/java/com/automation/selenium/tests/SeleniumScenariosTest.java +++ b/src/test/java/com/automation/selenium/tests/SeleniumScenariosTest.java @@ -68,8 +68,8 @@ public class SeleniumScenariosTest extends BaseTest { * uses containsAll, not equals, so a new module on the demo site does not * break the test. */ - @Test(description = "Scenario: read a list of elements (the side menu)") - public void sideMenuListsTheCoreModules() { + @Test(description = "[TC-116] Scenario: read a list of elements (the side menu)") + public void tc116_sideMenuListsTheCoreModules() { // Log in and read the text of every side menu link List items = loginAsAdmin().menuItems(); // Show the full list in the report @@ -88,8 +88,8 @@ public void sideMenuListsTheCoreModules() { * redirects it to the login page; a 200 after that redirect still proves * the link leads somewhere. */ - @Test(description = "Scenario: find broken links (the side menu)") - public void sideMenuHasNoBrokenLinks() { + @Test(description = "[TC-117] Scenario: find broken links (the side menu)") + public void tc117_sideMenuHasNoBrokenLinks() { // Log in and read the href of every side menu link List links = loginAsAdmin().menuLinks(); // Guard: an empty list would make the loop below pass without checking anything @@ -117,8 +117,8 @@ public void sideMenuHasNoBrokenLinks() { * Select class cannot read them. The page opens the list, reads every * option and closes it again. */ - @Test(description = "Scenario: read the options of a custom drop-down") - public void userRoleDropdownOffersAdminAndEss() { + @Test(description = "[TC-118] Scenario: read the options of a custom drop-down") + public void tc118_userRoleDropdownOffersAdminAndEss() { // Log in, open Admin, read all options of the User Role drop-down List options = loginAsAdmin().openAdmin().roleOptions(); // Show them in the report @@ -132,8 +132,8 @@ public void userRoleDropdownOffersAdminAndEss() { * Actions class: logs in with keyboard events only (type, Tab, type, * Enter), the way a keyboard user would, instead of clicking the button. */ - @Test(description = "Scenario: keyboard actions with the Actions class (Tab and Enter)") - public void adminCanLogInWithTheKeyboard() { + @Test(description = "[TC-119] Scenario: keyboard actions with the Actions class (Tab and Enter)") + public void tc119_adminCanLogInWithTheKeyboard() { // Open the login page and log in using only key presses DashboardPage dashboard = openLoginPage().loginWithKeyboard(Config.get().username(), Config.get().password()); @@ -147,8 +147,8 @@ public void adminCanLogInWithTheKeyboard() { * share the session cookie, so the new tab is logged in without a second * login. */ - @Test(description = "Scenario: open a new tab and switch between windows") - public void dashboardOpensInASecondTab() { + @Test(description = "[TC-120] Scenario: open a new tab and switch between windows") + public void tc120_dashboardOpensInASecondTab() { // Log in in the first tab loginAsAdmin(); // A handle is the ID of a window/tab; keep the first one to come back to it diff --git a/src/test/java/com/automation/selenium/tests/SystemUsersTest.java b/src/test/java/com/automation/selenium/tests/SystemUsersTest.java index a6cce63..28cedc7 100644 --- a/src/test/java/com/automation/selenium/tests/SystemUsersTest.java +++ b/src/test/java/com/automation/selenium/tests/SystemUsersTest.java @@ -33,8 +33,8 @@ public class SystemUsersTest extends BaseTest { * Opens Admin from the side menu and checks the title and that users are listed. */ // groups = "smoke" is added to the class group, so this test is in both "admin" and "smoke" - @Test(groups = "smoke", description = "System Users page opens from the Admin menu") - public void systemUsersPageOpensFromMenu() { + @Test(groups = "smoke", description = "[TC-111] System Users page opens from the Admin menu") + public void tc111_systemUsersPageOpensFromMenu() { // Log in, then click Admin in the side menu SystemUsersPage users = loginAsAdmin().openAdmin(); @@ -47,8 +47,8 @@ public void systemUsersPageOpensFromMenu() { /** * Searches for user name "Admin" and checks that every listed user is Admin. */ - @Test(description = "Filtering by username lists only that user") - public void filterByUsername() { + @Test(description = "[TC-112] Filtering by username lists only that user") + public void tc112_filterByUsername() { // Log in, open Admin, type "Admin" into the Username filter, press Search (methods are chained) SystemUsersPage users = loginAsAdmin().openAdmin().filterByUsername("Admin").search(); @@ -63,8 +63,8 @@ public void filterByUsername() { /** * Searches for enabled admins and checks the role and status of every listed user. */ - @Test(description = "Filtering by role and status lists only matching users") - public void filterByRoleAndStatus() { + @Test(description = "[TC-113] Filtering by role and status lists only matching users") + public void tc113_filterByRoleAndStatus() { // Choose User Role = Admin and Status = Enabled in the drop-downs, then Search SystemUsersPage users = loginAsAdmin().openAdmin().filterByRole("Admin").filterByStatus("Enabled").search(); @@ -82,8 +82,8 @@ public void filterByRoleAndStatus() { /** * Searches for a user name that does not exist and checks the empty result. */ - @Test(description = "Searching for an unknown username finds no records") - public void unknownUsernameFindsNoRecords() { + @Test(description = "[TC-114] Searching for an unknown username finds no records") + public void tc114_unknownUsernameFindsNoRecords() { // Search for a name nobody has SystemUsersPage users = loginAsAdmin().openAdmin().filterByUsername("no.such.user.42").search(); @@ -96,8 +96,8 @@ public void unknownUsernameFindsNoRecords() { /** * Sets all three filters, presses Reset and checks that every filter is empty again. */ - @Test(description = "Reset clears every filter") - public void resetClearsFilters() { + @Test(description = "[TC-115] Reset clears every filter") + public void tc115_resetClearsFilters() { // Fill all three filters, then press Reset (no search needed) SystemUsersPage users = loginAsAdmin().openAdmin() .filterByUsername("Admin").filterByRole("Admin").filterByStatus("Enabled") diff --git a/src/test/java/com/automation/selenium/tests/TestNgFeaturesTest.java b/src/test/java/com/automation/selenium/tests/TestNgFeaturesTest.java index ab72296..3e3c2b1 100644 --- a/src/test/java/com/automation/selenium/tests/TestNgFeaturesTest.java +++ b/src/test/java/com/automation/selenium/tests/TestNgFeaturesTest.java @@ -44,8 +44,8 @@ public class TestNgFeaturesTest extends BaseTest { * mismatch together, instead of stopping at the first one. assertAll() * at the end is what fails the test; without it the failures are lost. */ - @Test(description = "TestNG: SoftAssert checks the whole login page in one run") - public void softAssertChecksTheLoginPage() { + @Test(description = "[TC-121] TestNG: SoftAssert checks the whole login page in one run") + public void tc121_softAssertChecksTheLoginPage() { // Open the login page LoginPage login = openLoginPage(); // A new SoftAssert per test: it remembers every failed check @@ -70,8 +70,8 @@ public void softAssertChecksTheLoginPage() { */ // The test passes only if this exception type is thrown and its message matches the regex @Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = ".*Invalid credentials.*", - description = "TestNG: expectedExceptions, a wrong password must stop with the login error") - public void wrongPasswordThrowsTheLoginError() { + description = "[TC-102] TestNG: expectedExceptions, a wrong password must stop with the login error") + public void tc102_wrongPasswordThrowsTheLoginError() { // Right user, wrong password: loginAs throws "Login as "Admin" failed: Invalid credentials" openLoginPage().loginAs(Config.get().username(), "wrong-password"); } @@ -80,19 +80,19 @@ public void wrongPasswordThrowsTheLoginError() { * priority: the lower number runs first within this class (the default is * 0), so this check runs before the dependent test below. */ - @Test(priority = 1, description = "TestNG: priority, the login page check runs first") - public void loginPageIsShown() { + @Test(priority = 1, description = "[TC-121] TestNG: priority, the login page check runs first") + public void tc121_loginPageIsShown() { // Open the login page and check its form is there assertTrue(openLoginPage().isDisplayed(), "The login form should be shown"); } /** - * dependsOnMethods: runs only if loginPageIsShown passed. Had it failed, + * dependsOnMethods: runs only if tc121_loginPageIsShown passed. Had it failed, * TestNG would skip this test, not fail it (see ReportDemoTest). */ - @Test(priority = 2, dependsOnMethods = "loginPageIsShown", - description = "TestNG: dependsOnMethods, log in only after the login page check passed") - public void adminLogsInAfterThePageCheck() { + @Test(priority = 2, dependsOnMethods = "tc121_loginPageIsShown", + description = "[TC-101] TestNG: dependsOnMethods, log in only after the login page check passed") + public void tc101_adminLogsInAfterThePageCheck() { // Log in and check the dashboard assertTrue(loginAsAdmin().isDisplayed(), "The dashboard should be shown"); } @@ -102,8 +102,8 @@ public void adminLogsInAfterThePageCheck() { * browser, and reports each run. Useful to catch a check that only * sometimes passes. */ - @Test(invocationCount = 2, description = "TestNG: invocationCount, a wrong password is rejected every time") - public void wrongPasswordIsRejectedEveryTime() { + @Test(invocationCount = 2, description = "[TC-102] TestNG: invocationCount, a wrong password is rejected every time") + public void tc102_wrongPasswordIsRejectedEveryTime() { // Submit a wrong password (submit does not expect the login to work) LoginPage login = openLoginPage().submit(Config.get().username(), "wrong-password"); @@ -118,8 +118,8 @@ public void wrongPasswordIsRejectedEveryTime() { * browser that BaseTest started for this thread. */ // 60_000 ms = 60 s (the underscore is only a digit separator for readability) - @Test(timeOut = 60_000, description = "TestNG: timeOut, the login must finish within 60 seconds") - public void loginFinishesWithinOneMinute() { + @Test(timeOut = 60_000, description = "[TC-122] TestNG: timeOut, the login must finish within 60 seconds") + public void tc122_loginFinishesWithinOneMinute() { // Log in and check the dashboard, all within the time limit assertTrue(loginAsAdmin().isDisplayed(), "The dashboard should be shown"); } @@ -129,8 +129,8 @@ public void loginFinishesWithinOneMinute() { * before it counts as failed, which covers a short outage of the demo * site. A passing run is never retried. */ - @Test(retryAnalyzer = RetryOnce.class, description = "TestNG: retryAnalyzer, retried once if the demo site has a hiccup") - public void adminLoginIsRetriedOnceOnFailure() { + @Test(retryAnalyzer = RetryOnce.class, description = "[TC-101] TestNG: retryAnalyzer, retried once if the demo site has a hiccup") + public void tc101_adminLoginIsRetriedOnceOnFailure() { // Log in and check the dashboard; on failure TestNG asks RetryOnce whether to run again assertTrue(loginAsAdmin().isDisplayed(), "The dashboard should be shown"); } @@ -144,8 +144,8 @@ public void adminLoginIsRetriedOnceOnFailure() { */ // Fill the method parameter from in testng.xml @Parameters("expectedTitle") - @Test(description = "TestNG: Parameters from testng.xml, with an Optional default") - public void titleMatchesTheSuiteParameter(@Optional(TITLE) String expectedTitle) { + @Test(description = "[TC-121] TestNG: Parameters from testng.xml, with an Optional default") + public void tc121_titleMatchesTheSuiteParameter(@Optional(TITLE) String expectedTitle) { // Open the login page openLoginPage(); // Show in the report which value was used (from testng.xml or the default)