The target readers of this book are developers responsible for implementation, automated testing, and CI/CD construction in the development and maintenance of this system. The contents described in this document relate to the application, development/CI/CD environment, and automated testing aspects of this system and its development and maintenance. Also, in these aspects, we define the deliverables created by developers, mainly rules for creating application source code, test scripts, build scripts, and configuration files, that is, implementation methods. What is subject to regularization is when describing source code in a file, dividing the described content into multiple files, dividing it into components according to the syntax of the programming language within the file, file placement directories, and their naming, etc. In this document, in the division of content described in this source code, a collection of described content of the same type is called a responsibility, and a collection assigned to the same type of responsibility is called a component. By visualizing the configuration of components and their interactions, we aim to help developers understand the rules.

applications

Here, we define an implementation method for applications to be developed in this system. First, define applications and their responsibilities and interactions. Next, the application is broken down into components and their responsibilities and interactions are defined. Finally, we will define the implementation and placement rules for each component.

application configuration

The system consists of two applications and a DB, Frontend and Backend.

Diagram
Figure 1. application configuration

Frontend is a web application implemented with SvelteKit. It provides users with a user interface that can be used in a browser. It also performs simple validation of user input and calls to web APIs.

Backend is a web API application implemented in Quarkus. Provides a web API for front-end applications. Within the Web API, strict validation of requests, DB transaction control, business logic, and DB access are performed.

component configuration

The components that make up the Frontend/Backend application of this system and their interactions are defined as follows.

Diagram
Figure 2. component configuration

The responsibilities of each component and the units that define the components are defined are defined as follows.

Table 1. Component responsibilities and defining units
component duties Defined units

page

It is responsible for arranging screen items, defining validations, handling screen events, and calling web APIs.

1 per screen (URL)

pageLoader

It is responsible for data acquisition and initialization of input items using the Web API during screen initialization.

1 per page

UIComponent

It is responsible for making the screen into parts.

Units that are easy to reuse

Controllers

It is responsible for defining web API endpoints.

1 per entity

Validator

It is responsible for correlation validation.

1 per Controller

service

It is responsible for transaction control and processing flow control within a transaction.

1 per entity

Logic

It is responsible for processing that does not require access to external resources during service processing.

1 per Service

Repository

Responsible for DB access and O/R mapping.

1 per entity

An object for passing data between components is defined as follows.

Table 2. An object for passing data
objects The data to be represented Defined units

Model

Frontend web API requests and responses

1 per DTO

DTO

Web API requests and responses in Backend

1 per entity

Entity

DB records

1 per DB table

Frontend

package configuration

The FRONT project package structure is based on the SvelteKit project structure.

📁 svqk-front
└── 📁 src
    ├── 📁 lib -- 1
    │   ├── 📁 arch -- 2
    │   │   └── 📁 (function)
    │   │       ├── 📄 *.svelte
    │   │       └── 📄 *.ts
    │   └── 📁 domain -- 3
    │       └── 📁 (domain)
    │           ├── 📄 *.svelte
    │           └── 📄 *.ts
    └── 📁 routes -- 4
        └── 📁 (path...)
            ├── 📄 +page.svelte
            └── 📄 +page.ts
  1. This is the directory where libraries are placed. The files under this can be imported using paths starting with `$lib`.

  2. This is a directory where libraries of common functions are placed. Under this, a directory is created for each function, and UIComponents and utilities are arranged.

  3. A directory where domain-specific libraries are located. Under this, a directory is created for each domain, and UIComponents and utilities are placed.

  4. This is the directory used by SvelteKit Routing. Under this, a directory is created for each URL hierarchy, and Page and PageLoader are arranged.

See the SvelteKit documentation for details on the SvelteKit project structure.

page

Page is a component responsible for arranging screen items, defining validations, handling screen events, and calling web APIs.

Page is used in SvelteKit routing +page.svelte Implement it in a file. +page.svelte The relationship between files and Routing is as follows.

  • +page.svelte Place the files at any level below src/routes.

  • +page.svelte The path below src/routes where the file is placed is the path of the URL to access this screen.

SvelteKit's routing and +page.svelte See the SvelteKit documentation for detailed file specifications.

The implementation details of Page are as follows.

+page.svelte
<script lang="ts">

  // script section (1)

</script>

 (2)
 <!-- markup section -->
1 The script section implements validation definitions, screen event handling, web API calls, etc. in TypeScript.
2 The markup section implements the arrangement of screen items using HTML+ Svelte syntax.

pageLoader

PageLoader is a component responsible for retrieving data and initializing input items using the Web API during screen initialization.

PageLoader is used with SvelteKit loading data +page.ts Implement it in a file.

  • +page.ts The file is on the screen +page.svelte Place it in the same directory as the file.

See the SvelteKit documentation for detailed specifications of SvelteKit's loading data.

+page.ts
import type { PageLoad } from './$types';
import { env } from '$env/dynamic/public';

(1)
export const load: PageLoad = async ({ fetch }) => {
  const response = await fetch(`${env.PUBLIC_BACKEND_URL}/api/hello/1`);
  const hello = await response.json();

  (2)
  return {
    hello
  };
};
1 Define a load function. The load function is executed when the URL of the corresponding screen is accessed.
2 The data required by Page is returned as an object.
+page.svelte
<script lang="ts">
  import type { PageData } from './$types';

  (1)
  let { data }: { data: PageData } = $props();
  const hello = data.hello;
</script>

(2)
<h1 id="message">{hello.message}</h1>
1 Declare variables to store objects returned by PageLoader.
2 Data acquired by PageLoader via this variable can be viewed.

Backend

package configuration

📁 svqk-back
└── 📁 src/main/java
    └── 📁 {project-package}
        ├── 📁 arch
        │   └── 📁 (function)
        ├── 📁 domain
        │   └── 📁 (domain)
        │       ├── 📄 *Service.java
        │       └── 📄 *Repository.java
        └── 📁 interfaces
            └── 📁 (domain)
                ├── 📄 *Controller.java
                └── 📄 *Dto.java

Controllers

Controller is a component that acts as an endpoint for the Web API. It receives an HTTP request from a client and returns an HTTP response to it. The processing flow within the Web API is controlled by controlling the execution order and input/output of the Validator, Factory, and Service. The Controller implementation uses Jakarta REST.

Controller implementation
package dev.aulait.svqk.interfaces.hello;

import dev.aulait.svqk.domain.hello.HelloEntity;
import dev.aulait.svqk.domain.hello.HelloService;
import jakarta.validation.Valid;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import lombok.RequiredArgsConstructor;

@Path(HelloController.HELLO_PATH) (1)
@RequiredArgsConstructor (2)
public class HelloController {

  private final HelloService helloService; (3)

  static final String HELLO_PATH = "hello";

  static final String HELLO_GET_PATH = "{id}";

  @GET
  @Path(HELLO_GET_PATH)
  public HelloDto get(@PathParam("id") int id) { (4)

    HelloEntity entity = helloService.find(id);

    return HelloDto.builder().id(entity.getId()).message(entity.getMessage()).build();
  }

  @POST
  public int save(@Valid HelloDto dto) { (4)

    HelloEntity entity = HelloEntity.builder().id(dto.getId()).message(dto.getMessage()).build();

    HelloEntity savedEntity = helloService.save(entity);

    return savedEntity.getId();
  }
}
1 @Path Set it to the Controller class. This makes this Controller class an endpoint for the Web API and is recognized by Quarkus.
2 @RequiredArgsConstructor Set it to the Controller class. This will automatically generate a constructor with a private final field as an argument during compilation.
3 The component used by the Controller is defined as a private final field. This field injects an instance of the corresponding component when Quarkus starts.
4 Define methods that serve as endpoints for the Web API. The method is an annotation representing the HTTP Method of the Web API ( @GET , @POST etc.) is set.

See the Quarkus guide for details on Jakarta REST features that can be used in Controller implementations.

service

Service is a component responsible for transaction processing. The flow of transaction processing is controlled by controlling the execution order and input/output of Repository and Logic. JTA and CDI are used to implement the Service.

Service implementation
package dev.aulait.svqk.domain.hello;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;

@ApplicationScoped (1)
@RequiredArgsConstructor (2)
public class HelloService {

  private final HelloRepository helloRepository; (3)

  public HelloEntity find(int id) { (4)

  }

  @Transactional
  public HelloEntity save(HelloEntity entity) { (4)

  }
}
1 @ApplicationScoped Set it to the Service class. This allows instances of the Service class to be used as CDI managed beans.
2 @RequiredArgsConstructor Set it to the Service class. This makes it possible to use a constructor with a private final field as an argument without implementing it.
3 Define components used by the service as private final fields. This field injects the corresponding component instance when Quarkus starts.
4 A method is defined for each transaction process. What is the processing method for writing to the DB @Transactinal Set it up.

See the Quarkus documentation for details on JTA features available with Quarkus.

Repository

Repository is a component responsible for DB access and O/R mapping. The Repository implementation uses JPA and Spring Data JPA.

Repository implementation
package dev.aulait.svqk.domain.hello;

import org.springframework.data.jpa.repository.JpaRepository;

public interface HelloRepository extends JpaRepository<HelloEntity, Integer> { (1)
}
1 JpaRepository It inherits Type parameters specify entity and entity ID types.

For details on JPA and Spring Data JPA features available in Quarkus, see the Quarkus documentation.

DTO

DTO is an object representing Web API requests and responses.

Implementing DTO
package dev.aulait.svqk.interfaces.hello;

import lombok.Builder;
import lombok.Data;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

@Data (1)
@Builder
public class HelloDto {
  @Schema(required = true, readOnly = true) (2)
  private Integer id;

  private String message;
}
1 @Data Set it to the DTO class. Thus, field getters, setters, etc. are automatically generated.
2 Define fields that are properties of requests and responses.
  • For fields that must appear in the JSON properties of requests and responses required = true Specify Fields, etc. that store the value of a NOT NULL column fall under this category. ** For fields that do not use the value specified in the request in the internal processing of the Web API readOnly = true Specify Fields, etc. that store primary key values fall under this category.

development environment

Development environment configuration

Diagram
Figure 3. Development environment configuration

Project structure

This system stores materials (source files, configuration files) by dividing them into projects for each role. The project and main storage materials are as follows.

Table 3. Project and main storage materials
projects Main storage materials

svqk

Materials related to application development and testing are stored as child projects for each.

  • pom.xml
    Define properties that can be used in each project, such as DB connection information

  • .vscode/tasks.json, launch.json
    Define VSCode tasks and startup configurations

  • root.code-workspace
    Various VSCode settings

svqk-container

It stores materials for creating containers to be used in local development environments and CI.

  • compose.yml
    Define containers to be used in local development environments and CI

  • .env
    configuration file for propagating Maven properties to the container

  • pom.xml
    Integrate container startup into Maven's build process

svqk-migration

Stores materials for DB migration using Flyway.

  • pom.xml
    Flyway Maven Plugin settings

  • v*.sql, V*.java
    Flyway migration script

svqk-entity

Generate JPA Entities and store materials for making them into libraries.

  • pom.xml
    Configuring the JPA Entity Generator Maven Plugin

  • jeg-cofig.yml
    JPA Entity Generator configuration files

  • *entity.java
    Entity Java files generated by JPA Entity Generator

svqk-back

Stores Backend materials. This project is based on one created with the Quarkus Maven Plugin.

  • pom.xml
    Definition of the library (dependency) used by Backend Quarkus Maven Plugin settings

  • application.properties
    Quarkus configuration files

  • *.java
    Backend source files

svqk-front

Stores Frontend materials. This project is based on one created from a SvelteKit template.

  • package.json
    Definition of the library (dependency) used by Frontend Configuring SvelteKit to run API Client generation settings

  • svelte.config.js
    SvelteKit configuration files

  • *.svelte, *.ts
    Frontend source files

  • pom.xml
    Integrate Node module installations and Frontend builds into Maven's build process

svqk-e2etest

Stores materials for end to end tests. This project is based on something created from a Playwright template.

  • package.json
    Playwright execution settings

  • playwright.config.ts
    Playwright configuration files

  • *.ts
    Playwright test script

  • pom.xml
    Integrate Playwright execution into Maven's build process

  • compose.yml
    Defining containers for use in end to end

Build Configurations

This section explains the processing within the setup command and how to implement it.

setup command
# Windows
mvnw install -P setup

#macOS
./mvnw install -P setup

When the setup command is executed, each project is built sequentially. The process for these builds is shown below.

Table 4. Handling build commands for each project
Diagram
  1. The container project uses Docker to start the DB.

  2. The migration project uses Flyway to perform DB migrations.

  3. The FRONT project builds Frontend, which is a SveltKit app.

  4. The BACK project builds Backend, which is a Quarkus app.

  5. The BACK project runs unit/integration tests against BACKEND.

  6. The e2eTest project uses playwright to perform end to end tests.

The commands executed in each project build and their processing details are as follows. (Commands are listed from top to bottom in order of execution.)

Table 5. Commands executed within the setup command process and their processing details
Project Command process

container

MVNW RESOURCES: resources
Docker compose up

Creating an.env file used by Docker Compose
Start the DBMS container

migration

MVNW Flyway: Clean
MVNW Flyway: Migrate
MVNW Flyway: info

DB Schema Initialization
Run DB migrations
Show DB migration status

Front

pnpm install
Vite build
MVNW RESOURCES: resources
mvnw jar: jar

Installing packages used by the front project
Build Frontend (SvelteKit app)
Create a.env file for use by the front project
Create Frontend WebJAR

Entity

mvnw compiler:compile
mvnw jar: jar

Compile entity Java files
Create a JAR file containing the Entity class

Buck

MVNW RESOURCES: resources
mvnw compiler:compile
mvnw surefire: test
mvnw jar: jar
MVNW QUARKUS: BUILD
mvnw failsafe: integration-test

Create an application.properties file for Quarkus to use
Compiling Backend Java Files
Run unit tests
Create Backend JAR file
Create Backend container images
Run Integration Test (Web API Test)

e2etest

pnpm install
Playwright install
MVNW RESOURCES: resources
Docker compose up
Playwright test
Docker compose down

Installing packages used by the e2etest project
Installing the browser Playwright uses
Create a.env file to be used by the e2etest project
Start the Backend container
Run the End to End Test (Playwright Test)
Stop/delete Backend containers

The above command is Maven's Build life cycle It has been integrated into.

container

Here, I will explain the container project build configuration.

Table 6. Processing for each container project build phase
Build phase process

Process-resources

The Maven Resources Plugin copies the src/main/resources/.env file directly under the container project. At this time, some environment variables (DB connection information, etc.) in the.env file are replaced with project property values using the Filter function of the plug-in.

src/main/resources/.env file contents
DB_USER=${db.username}
.env content copied directly under the container project
DB_USER=svqk

This.env file is intended to be used as an environment variable for Docker Compose which is subsequently launched.

compile

The Maven Exec Plugin will execute the following command.

docker compose up -d --wait

This will start Docker Compose according to the compose.yml directly under the container project, and the DBMS can be used.

migration

Here, I will explain the migration project build configuration. Since the migration project has set the container project as dependency, it is executed after the container project is built.

Table 7. Processing for each migration project build phase
Build phase process

compile

If the setup profile is valid, the Flyway Maven Plugin clean , migrate , info Execute the goal.

Front

Here, I will explain the build configuration of the front project.

Table 8. Processing for each build phase of the front project
Build phase process

Generate-sources

If the setup profile is valid, the Maven Exec Plugin will execute the following command.

pnpm install

Generate-sources

The Maven Exec Plugin will execute the following command.

pnpm build

The pnpm build command builds the FRONT project as an SPA. The materials after the build are generated in the build directory directly under the front project.

Process-resources

The Maven Resources Plugin will copy the following.

  • Copy the resources/.env file directly under the front project. At this time, some environment variables (Backend port numbers, etc.) in the.env file are replaced with project property values using the Filter function of the plug-in.

resources/.env file contents
PUBLIC_BACKEND_URL=http://localhost:${back.port}
.env content copied directly under the front project
PUBLIC_BACKEND_URL=8081

process-classes

The Maven Resources Plugin will copy the following.

  • Copy all files under the build directory to target/classes/meta-inf/resources/webjars/front. The target/classes directory archives jar files, and the following meta-inf/resources/webjars/front hierarchy is for using jar files as WebJars.

References: Webjars

Pacakge

The Maven JAR Plugin will generate a JAR file containing materials after the FRONT project has been built.

Buck

Here, I will explain the back project build configuration. Since the back project has set the front project as dependency, it is executed after the front project is built.

Table 9. Processing for each build phase of a back project
Build phase process

Process-resources

The Maven Resources Plugin copies the src/main/resources/application.properties file into the target/classes directory. At this time, some property values (DB connection information, etc.) in the application.properties file are replaced with project property values using the Filter function of the plug-in.

src/main/resources/application.properties file
quarkus.datasource.username=${db.username}
application.properties file copied to target/classes directory
quarkus.datasource.username=svqk

package

The Maven JAR Plugin builds the BACK project and generates a JAR file.
The Quarkus Maven Plugin uses this JAR file to build the Backend container image. This container image also includes WebJars, which is the build material for the FRONT project.

Integration-test

Maven Failsafe Plugn runs test classes (*it.java) under the src/integration-test/java directory. The test class assumes the @QuarkusIntegrationTest annotation has been declared. This is intended to automatically start Quarkus when running integration-test.

e2etest

Here, I will explain the build structure of the e2etest project. Since the e2etest project has set the back project as a dependency, it runs after the back project is built.

Table 10. Processing for each build phase of the e2etest project
Build phase process

Generate-sources

If the setup profile is valid, the Maven Exec Plugin will execute the following command.

pnpm install
pnpm playwright-install

Process-resources

The Maven Resources Plugin copies the resources/.env file directly under the e2etest project. At this time, some strings in the.env file are replaced with project property values by the filter function of the plug-in.

Pre-integration-testing

The Maven Exec Plugin will execute the following command.

docker compose up -d --wait

As a result, Docker Compose is started according to the compose.yml directly under the e2etest project, and the Backend container can be used.

Integration-test

The Maven Exec Plugin will execute the following command.

pnpm e2etest

This will run PlayWright tests (*.spec.ts) under the tests directory.

post-intgration-test

The Maven Exec Plugin will execute the following command.

docker compose down

# on browse-e2etest profile
playwright show-report

This will stop and remove the Backend container. Also, if the browse-e2etest profile is valid, the browser will launch and the Playwright test report will be displayed.

Local development environment

execution environment

Diagram
Figure 4. execution environment

Development support

formatter

The formatters used in this project are all VSCode extensions, and they are configured so that the format is automatically executed when the source file is saved in VSCode. The formatter for each language in the source file is as follows.

Table 11. The language and the formatter used
languages Formatter (VSCode Extension)

html

esbenp.prettier-vscode

Java

josevseb.google-java-format-for-vs-code
(Google-java-format's v1.22.0 use)

Javascript

esbenp.prettier-vscode

json

esbenp.prettier-vscode

JSONC

esbenp.prettier-vscode

Svelte

esbenp.prettier-vscode

typescript

esbenp.prettier-vscode

xml

redhat.vscode-xml

yaml

redhat.vscode-yaml

Installing these formatters, applying them per language, and running them when saved are all It's set in VSCode's workspace settings file (.code-workspace).

tests

Test automation guidelines

In this system, automatic tests are performed in the following 3 stages.

  • Unit test

  • Integration test

  • End to end test

The methods and test targets for each test are defined below.

Diagram
Figure 5. automated test scope
  • Unit test
    Test components that don't depend on other components by running them in JUnit.

  • Integration test
    Test by running Backend's REST API from JUnit while Backend and DB are linked.

  • End to end test
    Test by operating Frontend with Playwright while Frontend, Backend, and DB are linked.

Integration test

Integration tests confirm that the REST API provided by Backend works according to specifications. This is done by automating REST API calls with a test program. The application structure for Integration Test is shown below.

Diagram
Figure 6. Integration Test application configuration

component configuration

The components that make up the Integration Test test program and their interactions are defined as follows.

Diagram
Figure 7. Test Program Component Structure

The responsibilities of each component and the units that define the components are defined are defined as follows.

Table 12. Component responsibilities and defining units
component duties File (class) units Method units

testCase

The REST API is called according to the test case and the expected values are verified.

1 for each Controller being tested

1 per test case

RestClient

It is responsible for calling the REST API and converting that request/response to DTO.

1 for each Controller being tested

1 per REST API call

DataFactory

We will generate a DTO containing the data required for the test.

1 for each Controller being tested

One for each data pattern required for testing

testCase

TestCase is a component responsible for calling REST APIs and verifying expected values according to test cases.

TestCase implementation
package dev.aulait.svqk.interfaces.issue;

import static org.junit.jupiter.api.Assertions.assertEquals;

import dev.aulait.svqk.arch.test.ConstraintViolationResponseDto;
import dev.aulait.svqk.arch.test.ValidationMessageUtils;
import io.quarkus.test.junit.QuarkusIntegrationTest;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Test;

@QuarkusIntegrationTest (1)
class IssueControllerIT {

  IssueClient client = new IssueClient(); (2)

  @Test (3)
  void testCrud() {
    IssueDto issue = IssueDataFactory.createRandomIssue(); (4)

    // Create
    int issueId = client.create(issue); (5)

    // Reference
    IssueDto createdIssue = client.get(issueId);
    assertEquals(issue.getSubject(), createdIssue.getSubject()); (6)

  }
}
1 Set @QuarkusIntegrationTest to the TestCase class.
2 Initialize the RestClient instance.
3 Following JUnit's approach, define a method and set @Test for each test case.
4 Use DataFactory to generate a DTO instance containing test data.
5 Use restClient to make REST API calls.
6 Expectations are verified using the JUnit API.
RestClient

RestClient is a component responsible for calling the REST API and converting that request/response to DTO.

RestClient implementation
package dev.aulait.svqk.interfaces.issue;

import static dev.aulait.svqk.arch.test.RestAssuredUtils.given;
import static dev.aulait.svqk.interfaces.issue.IssueController.*;

import dev.aulait.svqk.arch.test.ConstraintViolationResponseDto;

public class IssueClient {

  public Integer create(IssueDto issue) { (1)
    return Integer.parseInt(
        given() (2)
            .body(issue)
            .post(ISSUES_PATH)
            .then()
            .statusCode(200)
            .extract()
            .asString());
  }
1 Define methods to make REST API calls. The method argument is request dTO, and the return type is response dTO.
2 Implement REST API calls using RestAssured's API.
DataFactroy

DataFactroy is a component responsible for generating DTOs that store data required for testing.

Implementing DataFactory
package dev.aulait.svqk.interfaces.issue;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.RandomStringUtils;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class IssueDataFactory {

  public static IssueDto createRandomIssue() { (1)
    IssueDto issue = new IssueDto();
    issue.setSubject("test subject: " + RandomStringUtils.randomAlphanumeric(5));

    IssueStatusDto status = new IssueStatusDto();
    status.setId("1");
    issue.setIssueStatus(status);

    TrackerDto tracker = new TrackerDto();
1 Define a method for each data pattern required for the test. The example above is subject A random character string was stored in IssueDto It defines a method to create an instance.

End to end test

End to end testing verifies that the system works according to the use case scenario. This is done by automating user operations with a test program. The application structure for End to End Test is shown below.

Diagram
Figure 8. End to End Test application configuration

The above is an application configuration defined by the application architecture, in which the user was replaced with a test program.

component configuration

The components that make up the test program and their interactions are defined as follows.

Diagram
Figure 9. Test Program Component Structure

The responsibilities of each component and the units that define the components are defined are defined as follows.

Table 13. Component responsibilities and defining units
component duties File (class) units Method units

Spec

Run test scenarios using Facace and PageObject.

Units that make it easy to organize test scenarios

1 per test scenario

Facade

Use PageObject to perform a series of screen operations and expected value verification across multiple screens.

Units that make it easy to organize screens to be operated

One for each set of operations that should be reused in multiple scenarios

pageObject

Use PageElement to perform a series of screen operations and verification of expected values within a single screen.

1 per screen

One for each series of operations within the same screen

pageelement

Use the API provided by basePageElement to perform screen manipulation and expected value verification.

1 per screen

screen element + one per operation/verification

Factory

Generate models to be used in tests.

1 per model

One for each data pattern to be set in the model to be created

Spec

Spec is a component responsible for executing test scenarios. Test scenarios are implemented in Spec as Playwright test cases. In the test case, processing of screen operation and verification of expected values in line with the test scenario is implemented using Facace and PageObject.

Spec implementation
import { test } from '@playwright/test';
import { IssueFacade } from '../facades/IssueFacade';
import { DryRun } from '../arch/DryRun';
import TopPage from '../pages/top/TopPage';
import IssueInputFactory from '../factories/IssueFactory';

(1)
test('CRUD of Issue', async ({ page }) => {
  (2)
  const dryRun = DryRun.build();
  const topPage = new TopPage(page, dryRun);

  (3)
  const menuBar = await topPage.open();
  let issueListPage = await menuBar.gotoIssueListPage();
  let issueInputPage = await issueListPage.gotoNewIssuePage();

  // Create
  const issue = IssueInputFactory.createRandomIssue();
  await issueInputPage.save(issue);

  // Rererence
  const issueFacade = new IssueFacade(dryRun);
  issueInputPage = await issueFacade.referenceIssueBySubject(menuBar, issue.subject);

  // ...
});
  1. Define test cases using the test function provided by PlayWright. This is the entry point for tests that can be executed.

  2. Initialize the components required to run the test scenario.

  3. Implement test scenarios using facades and pageObjects. The 3 lines in the example above implement the scenario “open the top page, operate the menu bar to transition to the ticket list screen, and operate the ticket list screen to transition to the ticket registration screen”.

Facade

Facade is a component that is responsible for a series of screen operations and verification of expected values across multiple screens. Facade implements methods to do these using PageObject. The method is implemented so that the PageObject of the screen at the start is the argument, and the PageObject of the screen at the end is the return value.

Facade implementation
import BaseFacade from '../arch/BaseFacade';
import MenuBar from '../pages/menu-bar/MenuBar';

export class IssueFacade extends BaseFacade {
  (1)
  async referenceIssueBySubject(menuBar: MenuBar, subject: string) {
    this.logStart('Issue Reference');

    (2)
    const issueListPage = await menuBar.gotoIssueListPage();
    await issueListPage.searchIssue({ text: subject });
    const issueInputPage = await issueListPage.gotoIssueBySubject(subject);

    (3)
    return issueInputPage;
  }
}
  1. The Facade method has the following arguments.

    • PageObject of the screen that is the starting point for screen operations performed by Facade

    • Parameters used for screen operations performed by Facade

  2. Implement screen operations performed by Facade using PageObject.

  3. The PageObject of the screen at the end is returned as the return value of the method.

pageObject

PageObject is a component responsible for a series of screen operations within a screen and verification of expected values. PageObject implements methods that do these using PageElement.

PageObject implementation
import IssueInputPageElement from './IssueInputPageElement';
import type { IssueModel } from '../../api/Api';
import BasePageElement from '../../arch/BasePageElement';

export default class IssueInputPage {
  private issueInputPageEl: IssueInputPageElement;

  constructor(page: BasePageElement) {
    (1)
    this.issueInputPageEl = new IssueInputPageElement(page);
  }

  (2)
  async save(issue: IssueModel) {
    (3)
    await this.issueInputPageEl.inputSubject(issue.subject);
    await this.issueInputPageEl.inputDescription(issue.description!);

    await this.issueInputPageEl.clickSaveBtn();
  }

  (4)
  async expectIssue(issue: IssueModel) {
    await this.issueInputPageEl.expectSubject(issue.subject);
    await this.issueInputPageEl.expectDescription(issue.description);
  }
}
  1. Define methods to perform screen operations. In the example above, “was specified as an argument issue It defines a “method that performs an operation to save”.

  2. Use PageElement to implement a series of screen operations.

  3. Define methods for validating expected values. In the example above, “was specified as an argument issue It defines a “method that verifies whether the screen state matches with”.

pageelement

PageElement is a component responsible for manipulating screen elements and verifying expected values. PageElemnt implements methods to do these using basePageElement.

PageElement implementation
import BasePageElement from '../../arch/BasePageElement';

(1)
export default class IssueInputPageElement extends BasePageElement {
  get pageNameKey() {
    return 'newIssue';
  }

  (2)
  async inputSubject(subject: string) {
    (3)
    await this.inputText('#subject', subject);
  }

  (4)
  async expectSubject(subject: string) {
    await this.expectText('#subject', subject);
  }

}
  1. It inherits basePageElement, which is the common base class for PageElement. This allows developers to implement PageElement without being aware of PlayWright's API.

  2. An operation for one screen item is defined as 1 method. In the example above” 題名 It defines a “method for inputting a string specified as an argument into an item”.

  3. Implement operations on screen elements using the APIs provided by basePageElement. In the example above, “locator #subject An operation called “inputting a character string specified as an argument to the screen element specified by” has been implemented.

  4. The verification of expected values for a single screen item is defined as 1 method.

Factory

Factory is a component responsible for generating models to be used in tests.

Factory implementation
import type { IssueModel, IssueStatusModel } from '../api/Api';
import StringUtils from '../arch/StringUtils';

export default class IssueFactory {
  static create() {
    const issue = { issueStatus: {} as IssueStatusModel } as IssueModel;
    return issue;
  }

  (1)
  static createRandomIssue() {
    const issue = this.create();
    issue.subject = StringUtils.generateRandomString();
    issue.description = `${issue.subject}の説明`;

    return issue;
  }
}
  1. Implement a method to generate a Model to be used for testing. In the example above 題名 , 説明`にランダムな文字列を持つ `issue The model is being generated.