feat: Introduce Playwright for UI testing
Instead of using Robot Framework which does not run during a `mvn install`. Playwright seems to be the better approach. We can now write them as normal JUnit test and they are executed during a build. Additionally this PR solves some interesting bugs found during writing Playwright tests: - A reset of a lesson removes all assignments as a result another user wouldn't see any assignments - If someone solves an assignment the assignment automatically got solved for a new user since the assignment included the `solved` flag which immediately got copied to new lesson progress. - Introduction of assignment progress linking a assignment not directly to all users.
This commit is contained in:
@ -51,7 +51,6 @@ public class Assignment {
|
||||
|
||||
private String name;
|
||||
private String path;
|
||||
private boolean solved = false;
|
||||
|
||||
@Transient private List<String> hints;
|
||||
|
||||
@ -75,8 +74,4 @@ public class Assignment {
|
||||
this.path = path;
|
||||
this.hints = hints;
|
||||
}
|
||||
|
||||
public void solved() {
|
||||
this.solved = true;
|
||||
}
|
||||
}
|
||||
|
@ -18,4 +18,9 @@ public record LessonName(String lessonName) {
|
||||
lessonName = lessonName.substring(0, lessonName.indexOf(".lesson"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lessonName;
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import org.owasp.webgoat.container.lessons.LessonName;
|
||||
import org.owasp.webgoat.container.session.Course;
|
||||
import org.owasp.webgoat.container.users.UserProgressRepository;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
@ -40,11 +39,9 @@ public class LessonProgressService {
|
||||
var userProgress = userProgressRepository.findByUser(username);
|
||||
var lesson = course.getLessonByName(lessonName);
|
||||
|
||||
Assert.isTrue(lesson != null, "Lesson not found: " + lessonName);
|
||||
|
||||
var lessonProgress = userProgress.getLessonProgress(lesson);
|
||||
return lessonProgress.getLessonOverview().entrySet().stream()
|
||||
.map(entry -> new LessonOverview(entry.getKey(), entry.getValue()))
|
||||
.map(entry -> new LessonOverview(entry.getKey().getAssignment(), entry.getValue()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,47 @@
|
||||
package org.owasp.webgoat.container.users;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import org.owasp.webgoat.container.lessons.Assignment;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@Entity
|
||||
@EqualsAndHashCode
|
||||
public class AssignmentProgress {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Getter
|
||||
@OneToOne(cascade = CascadeType.ALL)
|
||||
private Assignment assignment;
|
||||
|
||||
@Getter private boolean solved;
|
||||
|
||||
protected AssignmentProgress() {}
|
||||
|
||||
public AssignmentProgress(Assignment assignment) {
|
||||
this.assignment = assignment;
|
||||
}
|
||||
|
||||
public boolean hasSameName(String name) {
|
||||
Assert.notNull(name, "Name cannot be null");
|
||||
|
||||
return assignment.getName().equals(name);
|
||||
}
|
||||
|
||||
public void solved() {
|
||||
this.solved = true;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
this.solved = false;
|
||||
}
|
||||
}
|
@ -9,14 +9,12 @@ import jakarta.persistence.Id;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Version;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import org.owasp.webgoat.container.lessons.Assignment;
|
||||
import org.owasp.webgoat.container.lessons.Lesson;
|
||||
|
||||
/**
|
||||
@ -61,7 +59,7 @@ public class LessonProgress {
|
||||
@Getter private String lessonName;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
|
||||
private final Set<Assignment> assignments = new HashSet<>();
|
||||
private final Set<AssignmentProgress> assignments = new HashSet<>();
|
||||
|
||||
@Getter private int numberOfAttempts = 0;
|
||||
@Version private Integer version;
|
||||
@ -72,11 +70,11 @@ public class LessonProgress {
|
||||
|
||||
public LessonProgress(Lesson lesson) {
|
||||
lessonName = lesson.getId();
|
||||
assignments.addAll(lesson.getAssignments() == null ? List.of() : lesson.getAssignments());
|
||||
assignments.addAll(lesson.getAssignments().stream().map(AssignmentProgress::new).toList());
|
||||
}
|
||||
|
||||
public Optional<Assignment> getAssignment(String name) {
|
||||
return assignments.stream().filter(a -> a.getName().equals(name)).findFirst();
|
||||
private Optional<AssignmentProgress> getAssignment(String name) {
|
||||
return assignments.stream().filter(a -> a.hasSameName(name)).findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,14 +83,14 @@ public class LessonProgress {
|
||||
* @param solvedAssignment the assignment which the user solved
|
||||
*/
|
||||
public void assignmentSolved(String solvedAssignment) {
|
||||
getAssignment(solvedAssignment).ifPresent(Assignment::solved);
|
||||
getAssignment(solvedAssignment).ifPresent(AssignmentProgress::solved);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return did they user solved all solvedAssignments for the lesson?
|
||||
*/
|
||||
public boolean isLessonSolved() {
|
||||
return assignments.stream().allMatch(Assignment::isSolved);
|
||||
return assignments.stream().allMatch(AssignmentProgress::isSolved);
|
||||
}
|
||||
|
||||
/** Increase the number attempts to solve the lesson */
|
||||
@ -102,14 +100,14 @@ public class LessonProgress {
|
||||
|
||||
/** Reset the tracker. We do not reset the number of attempts here! */
|
||||
void reset() {
|
||||
assignments.clear();
|
||||
assignments.forEach(AssignmentProgress::reset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list containing all the assignments solved or not
|
||||
*/
|
||||
public Map<Assignment, Boolean> getLessonOverview() {
|
||||
return assignments.stream().collect(Collectors.toMap(a -> a, Assignment::isSolved));
|
||||
public Map<AssignmentProgress, Boolean> getLessonOverview() {
|
||||
return assignments.stream().collect(Collectors.toMap(a -> a, AssignmentProgress::isSolved));
|
||||
}
|
||||
|
||||
long numberOfSolvedAssignments() {
|
||||
|
@ -31,6 +31,7 @@ public class UserService implements UserDetailsService {
|
||||
throw new UsernameNotFoundException("User not found");
|
||||
} else {
|
||||
webGoatUser.createUser();
|
||||
// TODO maybe better to use an event to initialize lessons to keep dependencies low
|
||||
lessonInitializables.forEach(l -> l.initialize(webGoatUser));
|
||||
}
|
||||
return webGoatUser;
|
||||
|
Reference in New Issue
Block a user