Skip to content

Commit

Permalink
Merge branch 'feature/athena-modeling-module-documentation' of https:…
Browse files Browse the repository at this point in the history
…//github.com/ls1intum/Artemis into feature/athena-modeling-module-documentation
  • Loading branch information
matthiaslehnertum committed Jun 1, 2024
2 parents 1805357 + 92823b9 commit dbc3763
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 11 deletions.
115 changes: 115 additions & 0 deletions docs/dev/playwright.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
E2E Testing with Playwright
===========================

Set up Playwright locally
-------------------------

To run the tests locally, developers need to set up Playwright on their machines.
End-to-end tests test entire workflows; therefore, they require the whole Artemis setup - database, client, and server to be running.
Playwright tests rely on the Playwright Node.js library, browser binaries, and some helper packages.

1. Install dependencies:

First, navigate to the Playwright folder:

.. code-block:: bash
cd src/test/playwright
Then install the dependencies:

.. code-block:: bash
npm install
2. Customize Playwright configuration:

We need to configure Playwright to match our local Artemis setup and user settings. All configurations are stored in
the ``playwright.env`` file. The default configuration for an ICL setup looks as follows:

.. code-block:: text
PLAYWRIGHT_USERNAME_TEMPLATE=artemis_test_user_USERID
PLAYWRIGHT_PASSWORD_TEMPLATE=artemis_test_user_USERID
ADMIN_USERNAME=artemis_admin
ADMIN_PASSWORD=artemis_admin
ALLOW_GROUP_CUSTOMIZATION=true
STUDENT_GROUP_NAME=students
TUTOR_GROUP_NAME=tutors
EDITOR_GROUP_NAME=editors
INSTRUCTOR_GROUP_NAME=instructors
CREATE_USERS=true
BASE_URL=http://localhost:9000
EXERCISE_REPO_DIRECTORY=test-exercise-repos
Make sure ``BASE_URL`` matches your Artemis client URL and ``ADMIN_USERNAME`` and
``ADMIN_PASSWORD`` match your Artemis admin user credentials.

3. Configure test users

Playwright tests require users with different roles to simulate concurrent user interactions. You can configure
user IDs and check their corresponding user roles in the ``src/test/playwright/support/users.ts`` file. Usernames
are defined automatically by replacing the ``USERID`` part in ``PLAYWRIGHT_USERNAME_TEMPLATE`` with the
corresponding user ID. If users with such usernames do not exist, set ``CREATE_USERS`` to ``true`` on the
``playwright.env`` file for users to be created during the setup stage. If users with the same usernames but
different user roles already exist, change the user IDs to different values to ensure that new users are created
with roles defined in the configuration.

4. Setup Playwright package and its browser binaries:

Install Playwright browser binaries, set up the environment to ensure Playwright can locate these binaries, and
create test users (if creating users is enabled in the configuration) with the following command:

.. code-block:: bash
npm run playwright:setup
5. Open Playwright UI

To open the Playwright UI, run:

.. code-block:: bash
npm run playwright:open
This opens a graphical interface that allows you to run individual tests, test files, or test suites while observing
the test execution in a browser window.

Another way to run tests is through the command line. To run all tests in the command line, use:

.. code-block:: bash
npm run playwright:test
To run a specific test file, use:

.. code-block:: bash
npx playwright test <path_to_test_file>
If you want to run a specific test suite or a single test, add the ``-g`` flag to the previous command, followed by the
test suite name or test name.
For example, you can run the test suite "`Course creation`" located in the file ``CourseManagement.spec.ts`` using
the command:

.. code-block:: bash
npx playwright test src/test/playwright/tests/CourseManagement.spec.ts -g "Course creation"
Test parallelization
--------------------

Running tests in parallel may speed up test execution. We achieve this using Playwright's built-in parallelization
feature. By default, tests are configured to run in fully parallel mode. This means that all tests in all files are
executed in parallel. Test execution tasks are divided among worker processes. Each process runs a separate browser
instance and executes a subset of tests. The number of worker processes can be adjusted in the ``playwright.config.js``
file.

.. warning ::
Using more worker processes divides the available computing resources, giving each worker fewer resources. Using too
many workers can lead to resource contention, slowing down individual test execution and potentially causing
timeouts.
To run tests sequentially (one after another), set the ``workers`` option to ``1``. To run tests within each file
sequentially, while running test files in parallel, set the ``fullyParallel`` option to ``false``.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ All these exercises are supposed to be run either live in the lecture with insta
dev/testservers
dev/docker
dev/cypress
dev/playwright
dev/open-source
dev/local-moodle-setup-for-lti

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,15 @@ else if (previousVersion.isEqualTo(path.requiredVersion)) {
* If the 'DATABASECHANGELOG' table does not exist, implying that the database is not yet initialized, the method
* returns {@code null}, indicating that a full migration or initialization is required.
* <p>
* If the 'artemis_version' table does not exist or no version is recorded, this method throws a {@link RuntimeException},
* If the 'artemis_version' table does not exist, this method throws a {@link RuntimeException},
* signaling a critical migration issue that must be resolved by installing a specific version of the application
* (as mentioned in the thrown error message) before proceeding.
* <p>
* This method ensures that the application's database schema is compatible with the application's current version,
* adhering to the migration path requirements.
*
* @return The latest version string from the 'artemis_version' table if it exists.
* @throws RuntimeException If the 'DATABASECHANGELOG' table does not exist, or if retrieving the latest version fails.
* @throws RuntimeException If the 'artemis_version' table does not exist
*/
private String getPreviousVersionElseThrow() {
String error = "Cannot start Artemis because version table does not exist, but a migration path is necessary! Please start the release 5.12.9 first, otherwise the migration will fail";
Expand All @@ -172,9 +172,8 @@ private String getPreviousVersionElseThrow() {
if (result.next()) {
return result.getString("latest_version");
}
// if no version exists, we fail here
log.error(error);
System.exit(12);
// if no version is recorded in the table, we proceed with the startup
return null;
}
catch (SQLException e) {
if (StringUtils.containsIgnoreCase(e.getMessage(), "databasechangelog") && (e.getMessage().contains("does not exist") || (e.getMessage().contains("doesn't exist")))) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package de.tum.in.www1.artemis.service.connectors.localci.buildagent;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
Expand All @@ -27,10 +29,13 @@ static void processTestResultFile(String testResultFileString, List<BuildResult.
throws IOException {
TestSuite testSuite = mapper.readValue(testResultFileString, TestSuite.class);

if (testSuite.testCases() != null) {
// If the xml file is only one test suite, parse it directly
if (!testSuite.testCases().isEmpty()) {
processTestSuite(testSuite, failedTests, successfulTests);
}
else {
// Else, check if the file contains an outer <testsuites> element
// And parse the inner test suites
TestSuites suites = mapper.readValue(testResultFileString, TestSuites.class);
if (suites.testsuites() == null) {
return;
Expand Down Expand Up @@ -60,6 +65,10 @@ record TestSuites(@JacksonXmlElementWrapper(useWrapping = false) @JacksonXmlProp

@JsonIgnoreProperties(ignoreUnknown = true)
record TestSuite(@JacksonXmlElementWrapper(useWrapping = false) @JacksonXmlProperty(localName = "testcase") List<TestCase> testCases) {

TestSuite {
testCases = Objects.requireNonNullElse(testCases, Collections.emptyList());
}
}

@JsonIgnoreProperties(ignoreUnknown = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,17 +79,18 @@ void createJobs() {
// temporarily remove listener to avoid triggering build job processing
sharedQueueProcessingService.removeListener();

JobTimingInfo jobTimingInfo = new JobTimingInfo(ZonedDateTime.now(), ZonedDateTime.now().plusMinutes(1), ZonedDateTime.now().plusMinutes(2));
JobTimingInfo jobTimingInfo1 = new JobTimingInfo(ZonedDateTime.now().plusMinutes(1), ZonedDateTime.now().plusMinutes(2), ZonedDateTime.now().plusMinutes(3));
JobTimingInfo jobTimingInfo2 = new JobTimingInfo(ZonedDateTime.now(), ZonedDateTime.now().plusMinutes(1), ZonedDateTime.now().plusMinutes(2));
BuildConfig buildConfig = new BuildConfig("echo 'test'", "test", "test", "test", "test", "test", null, null, false, false, false, null);
RepositoryInfo repositoryInfo = new RepositoryInfo("test", null, RepositoryType.USER, "test", "test", "test", null, null);

job1 = new BuildJobQueueItem("1", "job1", "address1", 1, course.getId(), 1, 1, 1, BuildStatus.SUCCESSFUL, repositoryInfo, jobTimingInfo, buildConfig, null);
job2 = new BuildJobQueueItem("2", "job2", "address1", 2, course.getId(), 1, 1, 1, BuildStatus.SUCCESSFUL, repositoryInfo, jobTimingInfo, buildConfig, null);
job1 = new BuildJobQueueItem("1", "job1", "address1", 1, course.getId(), 1, 1, 1, BuildStatus.SUCCESSFUL, repositoryInfo, jobTimingInfo1, buildConfig, null);
job2 = new BuildJobQueueItem("2", "job2", "address1", 2, course.getId(), 1, 1, 1, BuildStatus.SUCCESSFUL, repositoryInfo, jobTimingInfo2, buildConfig, null);
String memberAddress = hazelcastInstance.getCluster().getLocalMember().getAddress().toString();
agent1 = new BuildAgentInformation(memberAddress, 1, 0, new ArrayList<>(List.of(job1)), false, new ArrayList<>(List.of(job2)));
BuildJobQueueItem finishedJobQueueItem1 = new BuildJobQueueItem("3", "job3", "address1", 3, course.getId(), 1, 1, 1, BuildStatus.SUCCESSFUL, repositoryInfo, jobTimingInfo,
BuildJobQueueItem finishedJobQueueItem1 = new BuildJobQueueItem("3", "job3", "address1", 3, course.getId(), 1, 1, 1, BuildStatus.SUCCESSFUL, repositoryInfo, jobTimingInfo1,
buildConfig, null);
BuildJobQueueItem finishedJobQueueItem2 = new BuildJobQueueItem("4", "job4", "address1", 4, course.getId() + 1, 1, 1, 1, BuildStatus.FAILED, repositoryInfo, jobTimingInfo,
BuildJobQueueItem finishedJobQueueItem2 = new BuildJobQueueItem("4", "job4", "address1", 4, course.getId() + 1, 1, 1, 1, BuildStatus.FAILED, repositoryInfo, jobTimingInfo2,
buildConfig, null);
var result1 = new Result().successful(true).rated(true).score(100D).assessmentType(AssessmentType.AUTOMATIC).completionDate(ZonedDateTime.now());
var result2 = new Result().successful(false).rated(true).score(0D).assessmentType(AssessmentType.AUTOMATIC).completionDate(ZonedDateTime.now());
Expand Down

0 comments on commit dbc3763

Please sign in to comment.