PracticeQAAutomation logoPracticeQAAutomation
Se

Java · TestNG

Digital Rights Management with Selenium

Sign in with OTP and roles, create API keys (secret shown once), register titles with 32-hex content keys, issue Widevine, PlayReady and FairPlay licence tokens with expiry, rental, device-binding and HDCP rules, then redeem them on simulated devices and audit every grant or denial.

App under test

🔐 KeyVault DRM

URL
/apps/drm
Admin
admin@keyvault.dev / Drm@123
OTP
135790
Other roles
ops@ · viewer@keyvault.dev
Key format
32 hex · device 16 hex

Scenarios to automate

Work top to bottom — each one adds a new technique.

  1. 1

    Two-step login & lockout

    Beginner

    Wrong password shows attempts left in drm-login-error; the 3rd failure locks the form (drm-unlock resets). Correct password leads to drm-otp-form; OTP 135790 signs in.

  2. 2

    Role-based access

    Intermediate

    ops@ and viewer@ see access-denied on API keys and Account; viewer@ gets tokens-readonly and a disabled generate-token button.

  3. 3

    Show-once secrets

    Intermediate

    create-key opens secret-modal with secret-value (kv_live_…); after secret-done only key-masked-<id> (last 4 chars) remains. revoke-<id> needs confirm-revoke.

  4. 4

    Hex validation

    Beginner

    save-title with a 31-char or non-hex key shows error-title-key (KV-201); generate-key fills 32 hex and key-length shows 32 / 32.

  5. 5

    Token rules

    Advanced

    Expiry +2592001 fails with KV-204 (max 30 days); play duration above the rental period fails with KV-205; a 15-char device ID fails with KV-202.

  6. 6

    Account-level blockers

    Intermediate

    No active API key → token-error KV-101; FairPlay disabled in Production → KV-301; balance 0 (drain-balance) → KV-402.

  7. 7

    Licence decisions

    Advanced

    In the simulator, a wrong device ID gives KV-403, HDCP on Chrome desktop gives KV-420, Widevine level 5 on a phone gives KV-421, and moving sim-offset past expiry gives KV-410.

  8. 8

    Reports & export

    Beginner

    Filter report-drm / report-result, search, walk report-next until disabled, and export-csv downloads keyvault-licences.csv.

Sample Selenium suite

A working starting point that finds elements every way Selenium can — by role, label, text, id, name, CSS, XPath and data-testid — so you practice them all. Pick your language, switch to data-testid only to compare, or try your own in the Locator Playground.

Language
KeyVaultDrmTest.java
import org.testng.annotations.*;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.util.Map;
import org.openqa.selenium.support.ui.*;
import java.time.Duration;
import static org.openqa.selenium.support.ui.ExpectedConditions.*;

// Locators in this suite: By.id ×4 · By.name ×5 · By.linkText ×4 · By.xpath ×4 · By.cssSelector ×1 · data-testid ×12
public class KeyVaultDrmTest {
  static final String BASE = "https://www.practiceqaautomation.com";
  WebDriver driver;
  WebDriverWait wait;

  // Small helpers: every action takes a By and waits for its element first (no Thread.sleep).
  By tid(String id) { return By.cssSelector("[data-testid='" + id + "']"); }
  WebElement ready(By by) { return centre(wait.until(elementToBeClickable(by))); }
  void click(By by) { ready(by).click(); }
  WebElement nth(String id, int n) { return centre(wait.until(numberOfElementsToBeMoreThan(tid(id), n)).get(n)); }
  void clickNth(String id, int n) { nth(id, n).click(); }
  // Scroll to the middle of the screen first: Selenium only scrolls an element to the edge,
  // where a sticky header or a fixed bottom bar can cover it (ElementClickInterceptedException).
  WebElement centre(WebElement e) {
    ((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView({block: 'center', inline: 'nearest'})", e);
    return e;
  }
  void type(By by, String text) { ready(by).sendKeys(text); }
  void select(By by, String value) { new Select(ready(by)).selectByValue(value); }
  void check(By by) { WebElement e = ready(by); if (!e.isSelected()) e.click(); }
  void expectText(By by, String text) { wait.until(textToBePresentInElementLocated(by, text)); }
  void expectVisible(By by) { wait.until(visibilityOfElementLocated(by)); }
  void expectNthText(String id, int n, String text) {
    wait.until(d -> d.findElements(tid(id)).size() > n && d.findElements(tid(id)).get(n).getText().contains(text));
  }

  @BeforeMethod
  public void setUp() {
    ChromeOptions options = new ChromeOptions();
    // Turn off Chrome's "Save password?" and "password found in a data breach" pop-ups: after a
    // login they take the keyboard and mouse away from the page, so later typing and clicks are lost.
    options.setExperimentalOption("prefs", Map.of(
        "credentials_enable_service", false,
        "profile.password_manager_enabled", false,
        "profile.password_manager_leak_detection", false));
    driver = new ChromeDriver(options);
    wait = new WebDriverWait(driver, Duration.ofSeconds(10));
  }

  @AfterMethod(alwaysRun = true)
  public void tearDown() { driver.quit(); }

  @Test
  public void issuesAWidevineTokenAndRedeemsIt() {
    driver.get(BASE + "/apps/drm");
    type(By.id("drm-email"), "admin@keyvault.dev");
    type(By.name("drm-password"), "Drm@123");
    click(By.xpath("//button[normalize-space()='Continue']"));
    type(tid("drm-otp"), "135790");
    click(By.xpath("//button[normalize-space()='Verify & sign in']"));
    wait.until(urlContains("/apps/drm/dashboard"));
    expectVisible(tid("setup-hint"));

    // Create an API key — its secret is shown only once
    click(By.linkText("Keys"));
    type(By.cssSelector("[placeholder='e.g. Storefront production']"), "Storefront");
    click(tid("create-key"));
    expectText(tid("secret-value"), "kv_live_");
    click(tid("secret-done"));

    // Issue a Widevine token bound to one device, requiring HDCP
    click(By.linkText("Tokens"));
    click(By.xpath("//button[normalize-space()='Widevine']"));
    select(By.id("tk-title"), "t1");
    type(By.name("tk-user"), "user-42");
    type(By.id("tk-device"), "a1b2c3d4e5f60718");
    check(By.id("tk-hdcp"));
    click(By.xpath("//button[normalize-space()='Generate token']"));
    expectText(tid("token-id"), "TKN-");
    expectText(tid("token-balance"), "499");

    // Redeem on an HDCP-capable phone, then on a desktop without HDCP
    click(By.linkText("Simulator"));
    select(By.name("sim-device"), "android-phone");
    type(By.name("sim-device-id"), "a1b2c3d4e5f60718");
    click(tid("redeem-token"));
    expectText(tid("sim-status"), "Licence granted");
    expectText(tid("licence-id"), "LIC-");
    select(By.name("sim-device"), "chrome-desktop");
    click(tid("redeem-token"));
    expectText(tid("sim-reason"), "KV-420");
    click(By.linkText("Reports"));
    expectNthText("report-row", 0, "KV-420");
  }
}

Practicing another tool?