Michael Kutz
Quality Engineer at REWE digital, Conference Speaker about QA & Agile, Founder of Agile QA Cologne meetup, Freelance QA Consultant
Michael Kutz
linkedin.com/in/micha-kutz
mkutz
stackoverflow.com/users/437621
@MichaKutz
đšâđ» Arbeitet be REWE digital in Köln als Entwickler/Ambassador
đ Mag (Test-) Automation
đ mag es CI/CD-Pipelines zu bauen und zu warten
đ€ Redet auf Konferenzen ĂŒber das alles
đ§ ist fasziniert von kognitiven Verzerrungen
đ ist ein passionierter LĂ€ufer
Ich kann den Code lesen und verstehe was er tut (und warum)
Ich verstehe wie die Code funktioniert
Ich kann einfach neue Tests hinzufĂŒgen
Ich kann Details schnell und einfach Àndern
Ich kann den Code einfach Debuggen
Ich kann nachvollziehen, was wĂ€hrend der AusfĂŒhrung passiert ist (Reports/Logs)
Ich kann Teile des Codes verwenden, um weitere Tests schneller zu schreiben
Ănderungen am Testprodukt können durch Ă€hnlichen Aufwand im Code umgesetzt werden
There is nothing either good or bad but thinking makes it so.
Hamlet, Act 2, Scene 2
interagiert mit
Test
Test 2
interagiert mit
den selben
Elemente
Lokatoren
Interaktionen
Werkzeuge
Zustand
Lokatoren
Interaktionen
Werkzeuge
Zustand
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
webDriver.get("https://automationpractice.com");
}
}
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
webDriver.get("https://automationpractice.com");
webDriver.findElement(By.linkText("Sign in")).click();
}
}
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
webDriver.get("https://automationpractice.com");
webDriver.findElement(By.linkText("Sign in")).click();
new WebDriverWait(webDriver, Duration.ofSeconds(5))
.until(elementToBeClickable(By.id("email_create")))
.sendKeys(emailAddress);
webDriver.findElement(By.name("SubmitCreate")).click();
}
}
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
webDriver.get("https://automationpractice.com");
webDriver.findElement(By.linkText("Sign in")).click();
new WebDriverWait(webDriver, Duration.ofSeconds(5))
.until(elementToBeClickable(By.id("email_create")))
.sendKeys(emailAddress);
webDriver.findElement(By.name("SubmitCreate")).click();
new WebDriverWait(webDriver, Duration.ofSeconds(6))
.until(elementToBeClickable(By.name("id_gender1")))
.click();
webDriver.findElement(By.name("customer_firstname")).sendKeys("Romeo");
webDriver.findElement(By.name("customer_lastname")).sendKeys("Montague");
webDriver.findElement(By.name("passwd")).sendKeys("jul13t");
new Select(webDriver.findElement(By.name("days")))
.selectByValue("14");
new Select(webDriver.findElement(By.name("months")))
.selectByValue("2");
new Select(webDriver.findElement(By.name("years")))
.selectByValue("1999");
}
}
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
webDriver.get("https://automationpractice.com");
webDriver.findElement(By.linkText("Sign in")).click();
new WebDriverWait(webDriver, Duration.ofSeconds(5))
.until(elementToBeClickable(By.id("email_create")))
.sendKeys(emailAddress);
webDriver.findElement(By.name("SubmitCreate")).click();
new WebDriverWait(webDriver, Duration.ofSeconds(6))
.until(elementToBeClickable(By.name("id_gender1")))
.click();
webDriver.findElement(By.name("customer_firstname")).sendKeys("Romeo");
webDriver.findElement(By.name("customer_lastname")).sendKeys("Montague");
webDriver.findElement(By.name("passwd")).sendKeys("jul13t");
new Select(webDriver.findElement(By.name("days")))
.selectByValue("14");
new Select(webDriver.findElement(By.name("months")))
.selectByValue("2");
new Select(webDriver.findElement(By.name("years")))
.selectByValue("1999");
webDriver.findElement(By.name("firstname")).sendKeys("Romeo");
webDriver.findElement(By.name("lastname")).sendKeys("Montague");
webDriver.findElement(By.name("company")).sendKeys("Montague Ltd.");
webDriver.findElement(By.name("address1")).sendKeys("515 W Verona Ave");
webDriver.findElement(By.name("address2")).sendKeys("");
webDriver.findElement(By.name("city")).sendKeys("Verona");
new Select(webDriver.findElement(By.name("id_state"))).selectByVisibleText("Wisconsin");
webDriver.findElement(By.name("postcode")).sendKeys("53593");
webDriver.findElement(By.name("phone")).sendKeys("(202) 762-1401");
webDriver.findElement(By.name("phone_mobile")).sendKeys("");
webDriver.findElement(By.name("alias")).sendKeys("Home");
}
}
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
webDriver.get("https://automationpractice.com");
webDriver.findElement(By.linkText("Sign in")).click();
new WebDriverWait(webDriver, Duration.ofSeconds(5))
.until(elementToBeClickable(By.id("email_create")))
.sendKeys(emailAddress);
webDriver.findElement(By.name("SubmitCreate")).click();
new WebDriverWait(webDriver, Duration.ofSeconds(6))
.until(elementToBeClickable(By.name("id_gender1")))
.click();
webDriver.findElement(By.name("customer_firstname")).sendKeys("Romeo");
webDriver.findElement(By.name("customer_lastname")).sendKeys("Montague");
webDriver.findElement(By.name("passwd")).sendKeys("jul13t");
new Select(webDriver.findElement(By.name("days")))
.selectByValue("14");
new Select(webDriver.findElement(By.name("months")))
.selectByValue("2");
new Select(webDriver.findElement(By.name("years")))
.selectByValue("1999");
webDriver.findElement(By.name("firstname")).sendKeys("Romeo");
webDriver.findElement(By.name("lastname")).sendKeys("Montague");
webDriver.findElement(By.name("company")).sendKeys("Montague Ltd.");
webDriver.findElement(By.name("address1")).sendKeys("515 W Verona Ave");
webDriver.findElement(By.name("address2")).sendKeys("");
webDriver.findElement(By.name("city")).sendKeys("Verona");
new Select(webDriver.findElement(By.name("id_state"))).selectByVisibleText("Wisconsin");
webDriver.findElement(By.name("postcode")).sendKeys("53593");
webDriver.findElement(By.name("phone")).sendKeys("(202) 762-1401");
webDriver.findElement(By.name("phone_mobile")).sendKeys("");
webDriver.findElement(By.name("alias")).sendKeys("Home");
webDriver.findElement(By.name("submitAccount")).click();
}
}
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
webDriver.get("https://automationpractice.com");
webDriver.findElement(By.linkText("Sign in")).click();
new WebDriverWait(webDriver, Duration.ofSeconds(5))
.until(elementToBeClickable(By.id("email_create")))
.sendKeys(emailAddress);
webDriver.findElement(By.name("SubmitCreate")).click();
new WebDriverWait(webDriver, Duration.ofSeconds(6))
.until(elementToBeClickable(By.name("id_gender1")))
.click();
webDriver.findElement(By.name("customer_firstname")).sendKeys("Romeo");
webDriver.findElement(By.name("customer_lastname")).sendKeys("Montague");
webDriver.findElement(By.name("passwd")).sendKeys("jul13t");
new Select(webDriver.findElement(By.name("days")))
.selectByValue("14");
new Select(webDriver.findElement(By.name("months")))
.selectByValue("2");
new Select(webDriver.findElement(By.name("years")))
.selectByValue("1999");
webDriver.findElement(By.name("firstname")).sendKeys("Romeo");
webDriver.findElement(By.name("lastname")).sendKeys("Montague");
webDriver.findElement(By.name("company")).sendKeys("Montague Ltd.");
webDriver.findElement(By.name("address1")).sendKeys("515 W Verona Ave");
webDriver.findElement(By.name("address2")).sendKeys("");
webDriver.findElement(By.name("city")).sendKeys("Verona");
new Select(webDriver.findElement(By.name("id_state"))).selectByVisibleText("Wisconsin");
webDriver.findElement(By.name("postcode")).sendKeys("53593");
webDriver.findElement(By.name("phone")).sendKeys("(202) 762-1401");
webDriver.findElement(By.name("phone_mobile")).sendKeys("");
webDriver.findElement(By.name("alias")).sendKeys("Home");
webDriver.findElement(By.name("submitAccount")).click();
assertThat(webDriver.findElements(By.className("alert"))).isEmpty();
assertThat(webDriver.findElements(By.linkText("Sign in"))).isEmpty();
}
}
Tool
State
Interaction
Locator
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
webDriver.get("https://automationpractice.com");
webDriver.findElement(By.linkText("Sign in")).click();
new WebDriverWait(webDriver, Duration.ofSeconds(5))
.until(elementToBeClickable(By.id("email_create")))
.sendKeys(emailAddress);
webDriver.findElement(By.name("SubmitCreate")).click();
new WebDriverWait(webDriver, Duration.ofSeconds(6))
.until(elementToBeClickable(By.name("id_gender1")))
.click();
webDriver.findElement(By.name("customer_firstname")).sendKeys("Romeo");
webDriver.findElement(By.name("customer_lastname")).sendKeys("Montague");
webDriver.findElement(By.name("passwd")).sendKeys("jul13t");
new Select(webDriver.findElement(By.name("days")))
.selectByValue("14");
new Select(webDriver.findElement(By.name("months")))
.selectByValue("2");
new Select(webDriver.findElement(By.name("years")))
.selectByValue("1999");
webDriver.findElement(By.name("firstname")).sendKeys("Romeo");
webDriver.findElement(By.name("lastname")).sendKeys("Montague");
webDriver.findElement(By.name("company")).sendKeys("Montague Ltd.");
webDriver.findElement(By.name("address1")).sendKeys("515 W Verona Ave");
webDriver.findElement(By.name("address2")).sendKeys("");
webDriver.findElement(By.name("city")).sendKeys("Verona");
new Select(webDriver.findElement(By.name("id_state"))).selectByVisibleText("Wisconsin");
webDriver.findElement(By.name("postcode")).sendKeys("53593");
webDriver.findElement(By.name("phone")).sendKeys("(202) 762-1401");
webDriver.findElement(By.name("phone_mobile")).sendKeys("");
webDriver.findElement(By.name("alias")).sendKeys("Home");
webDriver.findElement(By.name("submitAccount")).click();
assertThat(webDriver.findElements(By.className("alert"))).isEmpty();
assertThat(webDriver.findElements(By.linkText("Sign in"))).isEmpty();
}
}
Zeilen Test Code | Zeilen Code insg. | Anzahl Dateien |
---|---|---|
48 | 48 | 1 |
Weitere Tests werden eine Ă€hnliche GröĂe und Struktur haben!
You speak an infinite deal of nothing.
The Merchant of Venice, Act 1, Scene 1
PageObject
nutzt
nutzt
zur Interaktion mit
Test
Test 2
Elemente
Lokatoren
Interaktionen
Werkzeuge
Zustand
Werkzeuge
Zustand
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
webDriver.get("https://automationpractice.com");
var homePage = new HomePage(webDriver);
}
}
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
webDriver.get("https://automationpractice.com");
var homePage = new HomePage(webDriver);
var signInPage = homePage.goToSignInPage();
}
}
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
webDriver.get("https://automationpractice.com");
var homePage = new HomePage(webDriver);
var signInPage = homePage.goToSignInPage();
var registerPage = signInPage.startRegistration(emailAddress);
}
}
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
webDriver.get("https://automationpractice.com");
var homePage = new HomePage(webDriver);
var signInPage = homePage.goToSignInPage();
var registerPage = signInPage.startRegistration(emailAddress);
registerPage.enterPersonalInformation(
"MR", "Romeo", "Montague", "jul13t",
LocalDate.of(1999, 2, 14));
}
}
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
webDriver.get("https://automationpractice.com");
var homePage = new HomePage(webDriver);
var signInPage = homePage.goToSignInPage();
var registerPage = signInPage.startRegistration(emailAddress);
registerPage.enterPersonalInformation(
"MR", "Romeo", "Montague", "jul13t",
LocalDate.of(1999, 2, 14));
registerPage.enterAddress(
"Romeo", "Montague",
"Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home");
}
}
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
webDriver.get("https://automationpractice.com");
var homePage = new HomePage(webDriver);
var signInPage = homePage.goToSignInPage();
var registerPage = signInPage.startRegistration(emailAddress);
registerPage.enterPersonalInformation(
"MR", "Romeo", "Montague", "jul13t",
LocalDate.of(1999, 2, 14));
registerPage.enterAddress(
"Romeo", "Montague",
"Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home");
registerPage.submit();
}
}
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
webDriver.get("https://automationpractice.com");
var homePage = new HomePage(webDriver);
var signInPage = homePage.goToSignInPage();
var registerPage = signInPage.startRegistration(emailAddress);
registerPage.enterPersonalInformation(
"MR", "Romeo", "Montague", "jul13t",
LocalDate.of(1999, 2, 14));
registerPage.enterAddress(
"Romeo", "Montague",
"Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home");
registerPage.submit();
assertThat(registerPage.getErrors()).isEmpty();
assertThat(homePage.isLoggedIn()).isTrue();
}
}
Details sind in den Page Objects
public record SignInPage(WebDriver webDriver) {
private static final By registerEmailInput = By.id("email_create");
private static final By registerButton = By.name("SubmitCreate");
public SignInPage {
new WebDriverWait(webDriver, Duration.ofSeconds(5))
.until(elementToBeClickable(registerEmailInput));
}
RegisterPage startRegistration(String emailAddress) {
webDriver.findElement(registerEmailInput).sendKeys(emailAddress);
webDriver.findElement(registerButton).click();
return new RegisterPage(webDriver);
}
}
Lokator
Interaktion
Lokator
Interaktion
public record HomePage(WebDriver webDriver) {
private static final By signInLink = By.linkText("Sign in");
SignInPage goToSignInPage() {
webDriver.findElement(signInLink).click();
return new SignInPage(webDriver);
}
boolean isLoggedIn() {
return webDriver.findElements(signInLink).isEmpty();
}
}
class RegisterTest {
WebDriver webDriver = WebDriverManager.chromedriver().create();
@Test
void register() {
webDriver.get("https://automationpractice.com");
var homePage = new HomePage(webDriver);
var signInPage = homePage.goToSignInPage();
var emailAddress =
"romeo-%s@shakespeareframework.org".formatted(randomUUID().getMostSignificantBits());
var registerPage = signInPage.startRegistration(emailAddress);
registerPage.enterPersonalInformation(
"MR", "Romeo", "Montague", "jul13t", LocalDate.of(1999, 2, 14));
registerPage.enterAddress(
"Romeo", "Montague",
"Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home");
registerPage.submit();
assertThat(registerPage.getErrors()).isEmpty();
assertThat(homePage.isLoggedIn()).isTrue();
}
}
Zeilen Test Code | Zeilen Code insg. | Anzahl Dateien |
---|---|---|
28 (-20) | 130 (+82) | 4 (+3) |
public record SignInPage(WebDriver webDriver) {
private static final By registerEmailInput = By.id("email_create");
private static final By registerButton = By.name("SubmitCreate");
public SignInPage {
new WebDriverWait(webDriver, Duration.ofSeconds(5))
.until(elementToBeClickable(registerEmailInput));
}
RegisterPage startRegistration(String emailAddress) {
webDriver.findElement(registerEmailInput).sendKeys(emailAddress);
webDriver.findElement(registerButton).click();
return new RegisterPage(webDriver);
}
}
public record RegisterPage(WebDriver webDriver) {
private static final Map<String, By> titleRadioButtons = Map.of(
"MR", By.id("id_gender1"),
"MRS", By.id("id_gender2"));
private static final By customerFirstNameInput = By.name("customer_firstname");
private static final By customerLastNameInput = By.name("customer_lastname");
private static final By passwordInput = By.name("passwd");
private static final By dateOfBirthDaySelect = By.name("days");
private static final By dateOfBirthMonthSelect = By.name("months");
private static final By dateOfBirthYearSelect = By.name("years");
private static final By errors = By.className("alert");
private static final By addressFirstNameInput = By.name("firstname");
private static final By addressLastNameInput = By.name("lastname");
private static final By companyInput = By.name("company");
private static final By addressLine1Input = By.name("address1");
private static final By addressLine2Input = By.name("address2");
private static final By cityInput = By.name("city");
private static final By stateSelect = By.name("id_state");
private static final By zipCodeInput = By.name("postcode");
private static final By homePhoneInput = By.name("phone");
private static final By mobilePhoneInput = By.name("phone_mobile");
private static final By addressAliasInput = By.name("alias");
private static final By submitButton = By.name("submitAccount");
public RegisterPage {
new WebDriverWait(webDriver, Duration.ofSeconds(6))
.until(ExpectedConditions.elementToBeClickable(customerFirstNameInput));
}
void enterPersonalInformation(
String title,
String firstName, String lastName,
String password,
LocalDate dateOfBirth) {
webDriver.findElement(titleRadioButtons.get(title)).click();
webDriver.findElement(customerFirstNameInput).sendKeys(firstName);
webDriver.findElement(customerLastNameInput).sendKeys(lastName);
webDriver.findElement(passwordInput).sendKeys(password);
new Select(webDriver.findElement(dateOfBirthDaySelect))
.selectByValue(Integer.toString(dateOfBirth.getDayOfMonth()));
new Select(webDriver.findElement(dateOfBirthMonthSelect))
.selectByValue(Integer.toString(dateOfBirth.getMonthValue()));
new Select(webDriver.findElement(dateOfBirthYearSelect))
.selectByValue(Integer.toString(dateOfBirth.getYear()));
}
void enterAddress(String firstName, String lastName, String company,
String addressLine1, String addressLine2,
String city, String state, String zipCode,
String homePhone, String mobilePhone,
String addressAlias) {
webDriver.findElement(addressFirstNameInput).sendKeys(firstName);
webDriver.findElement(addressLastNameInput).sendKeys(lastName);
webDriver.findElement(companyInput).sendKeys(company);
webDriver.findElement(addressLine1Input).sendKeys(addressLine1);
webDriver.findElement(addressLine2Input).sendKeys(addressLine2);
webDriver.findElement(cityInput).sendKeys(city);
new Select(webDriver.findElement(stateSelect)).selectByVisibleText(state);
webDriver.findElement(zipCodeInput).sendKeys(zipCode);
webDriver.findElement(homePhoneInput).sendKeys(homePhone);
webDriver.findElement(mobilePhoneInput).sendKeys(mobilePhone);
webDriver.findElement(addressAliasInput).sendKeys(addressAlias);
}
void submit() {
webDriver.findElement(submitButton).click();
}
public List<String> getErrors() {
return webDriver.findElements(errors).stream().map(WebElement::getText).toList();
}
}
Page Objects können wiederverw. werden
⊠mĂŒssen aber ggf. auch fĂŒr andere Tests angepasst werden
O Romeo, Romeo, wherefore art thou Romeo?
Romeo and Juliet, Act 2, Scene 2
Kein Benutzer-Konzept
Actor
Abilities
Elemente
Aktionen
ermöglichen
hat
dirigiert
zur Interaktion mit
Werkzeuge
Zustand
Screenplay
Test
class RegisterScreenplay {
Actor romeo = new Actor("Romeo");
}
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
}
Werkzeug
Zustand
Screenplay
Actor
Abilities
Elemente
Aktionen
ermöglichen
hat
dirigiert
zur Interaktion mit
Tasks
erledigt
bestehend aus
Test
Werkzeuge
Zustand
Lokatoren
Interaktionen
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
romeo
.does(new StartRegistration(emailAddress));
}
}
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
}
}
record StartRegistration(String emailAddress) implements Task {
@Override
public void performAs(Actor actor) {
var webDriver = actor.uses(BrowseTheWeb.class).getWebDriver();
}
}
record StartRegistration(String emailAddress) implements Task {
@Override
public void performAs(Actor actor) {
var webDriver = actor.uses(BrowseTheWeb.class).getWebDriver();
webDriver.findElement(By.linkText("Sign in")).click();
}
}
record StartRegistration(String emailAddress) implements Task {
@Override
public void performAs(Actor actor) {
var webDriver = actor.uses(BrowseTheWeb.class).getWebDriver();
webDriver.findElement(By.linkText("Sign in")).click();
new WebDriverWait(webDriver, Duration.ofSeconds(5))
.until(elementToBeClickable(By.id("email_create")))
.sendKeys(emailAddress);
webDriver.findElement(By.name("SubmitCreate")).click();
}
}
Tasks können sich ĂŒber mehrere Seiten erstrecken
Wir können mehr als eine Ability benutzen!
Tasks verbinden Aktionen mit Intentionen
Lokatoren
Interaktionen
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
romeo
.does(new StartRegistration(emailAddress))
.does(new EnterPersonalInformation(
"MR", "Romeo", "Montague",
"jul13t", LocalDate.of(1999, 2, 14)))
.does(new EnterAddress(
"Romeo", "Montague", "Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home"))
.does(new SubmitRegisterForm());
}
}
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
romeo
.does(new StartRegistration(emailAddress))
.does(new EnterPersonalInformation(
"MR", "Romeo", "Montague",
"jul13t", LocalDate.of(1999, 2, 14)))
.does(new EnterAddress(
"Romeo", "Montague", "Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home"));
}
}
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
romeo
.does(new StartRegistration(emailAddress))
.does(new EnterPersonalInformation(
"MR", "Romeo", "Montague",
"jul13t", LocalDate.of(1999, 2, 14)));
}
}
Wie können wir den Erfolg verifizieren?
Screenplay
Actor
Abilities
Elemente
Aktionen
ermöglichen
hat
dirigiert
zur Interaktion mit
Tasks
Questions
fragt
erledigt
ĂŒber den Zustand von
bestehend aus
Test
Werkzeuge
Zustand
Locatoren
Interaktionen
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
romeo
.does(new StartRegistration(emailAddress))
.does(new EnterPersonalInformation(
"MR", "Romeo", "Montague",
"jul13t", LocalDate.of(1999, 2, 14)))
.does(new EnterAddress(
"Romeo", "Montague", "Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home"))
.does(new SubmitRegisterForm());
assertThat(romeo.checks(new LoginStatus()))
.isTrue();
}
}
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
romeo
.does(new StartRegistration(emailAddress))
.does(new EnterPersonalInformation(
"MR", "Romeo", "Montague",
"jul13t", LocalDate.of(1999, 2, 14)))
.does(new EnterAddress(
"Romeo", "Montague", "Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home"))
.does(new SubmitRegisterForm());
}
}
public record LoginStatus() implements Question<Boolean> {
@Override
public Boolean answerAs(Actor actor) {
var webDriver = actor.uses(BrowseTheWeb.class).getWebDriver();
return webDriver.findElements(By.linkText("Sign in")).isEmpty();
}
}
Questions benutzen ebenfalls Abilities
Aber haben einen RĂŒckgabe-wert
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
romeo
.does(new StartRegistration(emailAddress))
.does(new EnterPersonalInformation(
"MR", "Romeo", "Montague",
"jul13t", LocalDate.of(1999, 2, 14)))
.does(new EnterAddress(
"Romeo", "Montague", "Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home"))
.does(new SubmitRegisterForm());
assertThat(romeo.checks(new LoginStatus()))
.isTrue();
}
}
Zeilen Test Code | Zeilen Code insg. | Anzahl Dateien |
---|---|---|
28 (±0) | 108 (-22) | 5 (+1) |
public record LoginStatus() implements Question<Boolean> {
@Override
public Boolean answerAs(Actor actor) {
var webDriver = actor.uses(BrowseTheWeb.class).getWebDriver();
return webDriver.findElements(By.linkText("Sign in")).isEmpty();
}
}
record StartRegistration(String emailAddress) implements Task {
@Override
public void performAs(Actor actor) {
var webDriver = actor.uses(BrowseTheWeb.class).getWebDriver();
webDriver.findElement(By.linkText("Sign in")).click();
new WebDriverWait(webDriver, Duration.ofSeconds(5))
.until(elementToBeClickable(By.id("email_create")))
.sendKeys(emailAddress);
webDriver.findElement(By.name("SubmitCreate")).click();
}
}
record EnterPersonalInformation(
String title, String firstName, String lastName, String password, LocalDate dateOfBirth)
implements Task {
@Override
public void performAs(Actor actor) {
var webDriver = actor.uses(BrowseTheWeb.class).getWebDriver();
new WebDriverWait(webDriver, Duration.ofSeconds(6))
.until(
elementToBeClickable(
switch (title) {
case "MR" -> By.id("id_gender1");
case "MRS" -> By.id("id_gender2");
default -> throw new IllegalArgumentException();
}))
.click();
webDriver.findElement(By.name("customer_firstname")).sendKeys(firstName);
webDriver.findElement(By.name("customer_lastname")).sendKeys(lastName);
webDriver.findElement(By.name("passwd")).sendKeys(password);
new Select(webDriver.findElement(By.name("days")))
.selectByValue(Integer.toString(dateOfBirth.getDayOfMonth()));
new Select(webDriver.findElement(By.name("months")))
.selectByValue(Integer.toString(dateOfBirth.getMonthValue()));
new Select(webDriver.findElement(By.name("years")))
.selectByValue(Integer.toString(dateOfBirth.getYear()));
}
}
record EnterAddress(
String firstName,
String lastName,
String company,
String addressLine1,
String addressLine2,
String city,
String state,
String zipCode,
String homePhone,
String mobilePhone,
String addressAlias)
implements Task {
@Override
public void performAs(Actor actor) {
var webDriver = actor.uses(BrowseTheWeb.class).getWebDriver();
webDriver.findElement(By.name("firstname")).sendKeys(firstName);
webDriver.findElement(By.name("lastname")).sendKeys(lastName);
webDriver.findElement(By.name("company")).sendKeys(company);
webDriver.findElement(By.name("address1")).sendKeys(addressLine1);
webDriver.findElement(By.name("address2")).sendKeys(addressLine2);
webDriver.findElement(By.name("city")).sendKeys(city);
new Select(webDriver.findElement(By.name("id_state"))).selectByVisibleText(state);
webDriver.findElement(By.name("postcode")).sendKeys(zipCode);
webDriver.findElement(By.name("phone")).sendKeys(homePhone);
webDriver.findElement(By.name("phone_mobile")).sendKeys(mobilePhone);
webDriver.findElement(By.name("alias")).sendKeys(addressAlias);
}
}
Men of few words are the best men.
Henry V, Act 3, Scene 2
Tasks und Questions sind wiederverw.
Sie dienen jedoch nur einem Zweck
"Tricks" werden immer im Kontext angewendet
Screenplay
Actor
Abilities
Elemente
Aktionen
ermöglichen
hat
dirigiert
zur Interaktion mit
Tasks
Questions
fragt
erledigt
ĂŒber den Zustand von
bestehend aus
Test
Werkzeuge
Zustand
Lokatoren
Interaktionen
Facts
ermöglichen
lernt
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
romeo
.does(new StartRegistration(emailAddress))
.does(new EnterPersonalInformation(
"MR", "Romeo", "Montague",
"jul13t", LocalDate.of(1999, 2, 14)))
.does(new EnterAddress(
"Romeo", "Montague", "Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home"))
.does(new SubmitRegisterForm());
assertThat(romeo.checks(new LoginStatus()))
.isTrue();
}
}
Viele potentielle Duplikate in Testdaten
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
romeo
.does(new StartRegistration(emailAddress))
.does(new EnterPersonalInformation(
"MR", "Romeo", "Montague",
"jul13t", LocalDate.of(1999, 2, 14)))
.does(new EnterAddress(
"Romeo", "Montague", "Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home"))
.does(new SubmitRegisterForm());
assertThat(romeo.checks(new LoginStatus()))
.isTrue();
}
}
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
var emailAddress = "romeo@shakespeareframework.org";
romeo
.does(new StartRegistration(emailAddress))
.does(new EnterPersonalInformation(
"MR", "Romeo", "Montague",
"jul13t", LocalDate.of(1999, 2, 14)))
.does(new EnterAddress(
"Romeo", "Montague", "Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home"))
.does(new SubmitRegisterForm());
assertThat(romeo.checks(new LoginStatus()))
.isTrue();
}
}
public record EmailAddress(
String address
) implements Fact {
public static EmailAddress defaultEmailAddress =
new EmailAddress("romeo@shakespeareframework.org");
}
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
romeo
.does(new StartRegistration(defaultEmailAddress))
.does(new EnterPersonalInformation(
"MR", "Romeo", "Montague",
"jul13t", LocalDate.of(1999, 2, 14)))
.does(new EnterAddress(
"Romeo", "Montague", "Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home"))
.does(new SubmitRegisterForm());
assertThat(romeo.checks(new LoginStatus()))
.isTrue();
}
}
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
romeo
.does(new StartRegistration(defaultEmailAddress))
.does(new EnterPersonalInformation(
"MR", "Romeo", "Montague",
"jul13t", LocalDate.of(1999, 2, 14)))
.does(new EnterAddress(
"Romeo", "Montague", "Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home"))
.does(new SubmitRegisterForm());
assertThat(romeo.checks(new LoginStatus()))
.isTrue();
}
}
public record PersonalInformation(
String title,
String firstName,
String lastName,
String password,
LocalDate dateOfBirth
) implements Fact {
public static final PersonalInformation defaultPersonalInformation =
new PersonalInformation(
"MR", "Romeo", "Montague",
"jul13t", LocalDate.of(1999, 2, 14));
}
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
romeo
.does(new StartRegistration(defaultEmailAddress))
.does(new EnterPersonalInformation(
defaultPersonalInformation))
.does(new EnterAddress(
"Romeo", "Montague", "Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home"))
.does(new SubmitRegisterForm());
assertThat(romeo.checks(new LoginStatus()))
.isTrue();
}
}
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
romeo
.does(new StartRegistration(defaultEmailAddress))
.does(new EnterPersonalInformation(
defaultPersonalInformation))
.does(new EnterAddress(
"Romeo", "Montague", "Montague Ltd.",
"515 W Verona Ave", "",
"Verona", "Wisconsin", "53593",
"(202) 762-1401", "",
"Home"))
.does(new SubmitRegisterForm());
assertThat(romeo.checks(new LoginStatus()))
.isTrue();
}
}
Facts können wiederverw. werden, enthalten Konstanten und Generatoren
public record Address(
String firstName,
String lastName,
String company,
String addressLine1,
String addressLine2,
String city,
String state,
String zipCode,
String homePhone,
String mobilePhone,
String addressAlias
) implements Fact {
public static final Address defaultAddress =
new Address(
"Romeo",
"Montague",
"Montague Ltd.",
"515 W Verona Ave",
"",
"Verona",
"Wisconsin",
"53593",
"(202) 762-1401",
"",
"Home");
}
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
romeo
.does(new StartRegistration(defaultEmailAddress))
.does(new EnterPersonalInformation(
defaultPersonalInformation))
.does(new EnterAddress(
defaultAddress))
.does(new SubmitRegisterForm());
assertThat(romeo.checks(new LoginStatus()))
.isTrue();
}
}
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"));
@Test
void register() {
romeo
.does(new StartRegistration(defaultEmailAddress))
.does(new EnterPersonalInformation(
defaultPersonalInformation))
.does(new EnterAddress(
defaultAddress))
.does(new SubmitRegisterForm());
assertThat(romeo.checks(new LoginStatus()))
.isTrue();
}
}
These tasks depend on each other
Let's group them together
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"))
.learns(defaultEmailAddress)
.learns(defaultPersonalInformation)
.learns(defaultAddress);
@Test
void register() {
romeo.does(new Register());
assertThat(romeo.checks(new LoginStatus()))
.isTrue();
}
}
Wo sind die Daten jetzt?
public record Register() implements Task {
@Override
public void performAs(Actor actor) {
actor
.does(new StartRegistration(
actor.remembers(EmailAddress.class)))
.does(new EnterPersonalInformation(
actor.remembers(PersonalInformation.class)))
.does(new EnterAddress(
actor.remembers(Address.class)))
.does(new SubmitRegisterForm());
}
}
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"))
.learns(defaultEmailAddress)
.learns(defaultPersonalInformation)
.learns(defaultAddress);
@Test
void register() {
romeo.does(new Register());
assertThat(romeo.checks(new LoginStatus()))
.isTrue();
}
}
Wie können wir wissen was genau fehlgeschlagen ist?
class RegisterScreenplay {
Actor romeo = new Actor("Romeo")
.can(new BrowseTheWeb(
new LocalWebDriverSupplier(BrowserType.CHROME),
"https://automationpractice.com"))
.informs(new Slf4jReporter())
.learns(defaultEmailAddress)
.learns(defaultPersonalInformation)
.learns(defaultAddress);
@Test
void register() {
romeo.does(new Register());
assertThat(romeo.checks(new LoginStatus()))
.isTrue();
}
}
INFO Romeo does Register[emailAddress=romeo@shakespeareframework.org] â 27s
âââ Romeo does StartRegistration[emailAddress=romeo@shakespeareframework.org] â 320ms
âââ Romeo does EnterPersonalInformation[personalInformation=MR Romeo Montague, born 1999-02-14] â 3s428ms
âââ Romeo does EnterAddress[address=Home
â Romeo Montague Montague Ltd.
â 515 W Verona Ave
â Verona Wisconsin 53593
â (202) 762-1401] â 939ms
âââ Romeo does SubmitRegisterForm[] â 6s
INFO Romeo checks LoginStatus[] â 37ms â true
WARN Romeo does Register[] â 3s787ms NoSuchElementException
âââ Romeo does StartRegistration[emailAddress=romeo@shakespeareframework.org] â 61ms NoSuchElementException
no such element: Unable to locate element: {"method":"link text","selector":"Sign in"}
(Session info: chrome=100.0.4896.127)
Tasks und Questions geben bei Fehlern Kontext
Ich kann den Code lesen und verstehe was er tut (und warum)
Ich verstehe wie die Code funktioniert
Ich kann einfach neue Tests hinzufĂŒgen
Ich kann Details schnell und einfach Àndern
Ich kann den Code einfach Debuggen
Ich kann nachvollziehen, was wĂ€hrend der AusfĂŒhrung passiert ist (Reports/Logs)
Ich kann Teile des Codes verwenden, um weitere Tests schneller zu schreiben
Ănderungen am Testprodukt können durch Ă€hnlichen Aufwand im Code umgesetzt werden
Email  michael.kutz@rewe-digital.com
Twitter @MichaKutz
Fragen?
đ©đȘ
đŹđ§
By Michael Kutz
The screenplay pattern can help you to write complex test code (especially but not only for UIs) in a readable, maintainable and strictly user-centric way using any given language and framework.
Quality Engineer at REWE digital, Conference Speaker about QA & Agile, Founder of Agile QA Cologne meetup, Freelance QA Consultant