Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion src/e2e/java/teammates/e2e/pageobjects/AppPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.Map;

import org.openqa.selenium.By;
import org.openqa.selenium.ElementClickInterceptedException;
import org.openqa.selenium.InvalidElementStateException;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
Expand Down Expand Up @@ -277,6 +278,17 @@ protected void click(WebElement element) {
executeScript("arguments[0].click();", element);
}

/**
* Native click when possible; falls back to a script click when a fixed header intercepts the click.
*/
protected void clickPreferNative(WebElement element) {
try {
element.click();
} catch (ElementClickInterceptedException e) {
click(element);
}
}

/**
* Simulates the clearing and sending of keys to an element.
*
Expand Down Expand Up @@ -623,7 +635,9 @@ public void clickDismissModalButtonAndWaitForModalHidden(WebElement dismissModal
void scrollElementToCenterAndClick(WebElement element) {
// TODO: migrate to `scrollIntoView` when Geckodriver is adopted
scrollElementToCenter(element);
element.click();
// Prefer a native click so the element receives focus (required for spreadsheet keyboard input and some
// Angular controls). Fall back to a script click when a fixed header still intercepts the click.
clickPreferNative(element);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

import teammates.common.util.Const;
import teammates.e2e.util.TestProperties;
import teammates.storage.sqlentity.Course;
import teammates.storage.sqlentity.Instructor;
Expand All @@ -20,6 +21,9 @@
* Represents the instructor course details page of the website.
*/
public class InstructorCourseDetailsPageSql extends AppPage {
private static final String DEFAULT_SECTION_DISPLAY_LABEL = "No specific section";
private static final String INSTRUCTOR_TEAM_DISPLAY_LABEL = "Instructors";

@FindBy(id = "course-id")
private WebElement courseIdField;

Expand Down Expand Up @@ -114,15 +118,35 @@ private String[][] getExpectedStudentValues(List<Student> students) {
String[][] expected = new String[students.size()][5];
for (int i = 0; i < students.size(); i++) {
Student student = students.get(i);
expected[i][0] = student.getSectionName();
expected[i][1] = student.getTeamName();
expected[i][0] = getSectionNameForUi(student.getSectionName());
expected[i][1] = getTeamNameForUi(student.getTeamName());
expected[i][2] = student.getName();
expected[i][3] = (student.getGoogleId() == null || student.getGoogleId().isEmpty()) ? "Yet to Join" : "Joined";
expected[i][4] = student.getEmail();
}
return expected;
}

/**
* Maps reserved API section name to the label shown in the student table (same as the web roster display).
*/
private String getSectionNameForUi(String sectionName) {
if (Const.DEFAULT_SECTION.equals(sectionName)) {
return DEFAULT_SECTION_DISPLAY_LABEL;
}
return sectionName;
}

/**
* Maps reserved API team name to the label shown in the student table (same as the web roster display).
*/
private String getTeamNameForUi(String teamName) {
if (Const.USER_TEAM_FOR_INSTRUCTOR.equals(teamName)) {
return INSTRUCTOR_TEAM_DISPLAY_LABEL;
}
return teamName;
}

private WebElement getSendInviteButton(String studentEmailAddress) {
WebElement studentRow = getStudentRow(studentEmailAddress);
return studentRow.findElement(By.cssSelector("[id^='btn-send-invite-']"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

import teammates.common.util.Const;
import teammates.storage.sqlentity.Student;

/**
Expand Down Expand Up @@ -52,7 +53,7 @@ public void verifyStudentDetails(Student student) {
assertEquals(student.getCourseId(), courseId.getText());
assertEquals(student.getName(), studentNameTextbox.getAttribute("value"));
if (student.getSection() == null) {
assertEquals("None", sectionNameTextbox.getAttribute("value"));
assertEquals(Const.DEFAULT_SECTION, sectionNameTextbox.getAttribute("value"));
} else {
assertEquals(student.getSectionName(), sectionNameTextbox.getAttribute("value"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1165,7 +1165,8 @@ private void selectVisibilityDropdownOption(int questionNum, String text) {

private void clickAndWaitForNewQuestion(WebElement button) {
int newQuestionNum = getNumQuestions() + 1;
click(button);
scrollElementToCenter(button);
clickPreferNative(button);
waitForElementPresence(By.id("question-form-" + newQuestionNum));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import teammates.common.datatransfer.questions.FeedbackResponseDetails;
import teammates.common.datatransfer.questions.FeedbackRubricQuestionDetails;
import teammates.common.datatransfer.questions.FeedbackRubricResponseDetails;
import teammates.common.util.Const;
import teammates.e2e.util.TestProperties;
import teammates.storage.sqlentity.FeedbackQuestion;
import teammates.storage.sqlentity.FeedbackResponse;
Expand All @@ -47,6 +48,8 @@ public class InstructorFeedbackResultsPageSql extends AppPage {
private static final String NO_RESPONSE_LABEL = "No Response";
private static final String NO_TEAM_LABEL = "No Specific Team";
private static final String NO_SECTION_LABEL = "No specific section";
/** User-visible label for {@link Const#USER_TEAM_FOR_INSTRUCTOR} (matches roster display on the web UI). */
private static final String INSTRUCTOR_TEAM_DISPLAY_LABEL = "Instructors";
private static final String NO_USER_LABEL = "No Specific User";

private static final String MCQ_OTHER = "Other";
Expand Down Expand Up @@ -388,8 +391,10 @@ private void verifyMcqStatistics(WebElement questionPanel, FeedbackQuestion ques
List<FeedbackResponse> responsesToUse = filterMissingResponses(responses);
List<WebElement> statisticsTables = questionPanel.findElements(By.cssSelector("#mcq-statistics table"));
verifyTableBodyValues(statisticsTables.get(0), getMcqResponseSummary(question));
// sort per recipient statistics
// sort per recipient statistics (re-query the table after click — sort re-renders and stale elements fail)
click(statisticsTables.get(1).findElements(By.tagName("th")).get(1));
waitUntilAnimationFinish();
statisticsTables = questionPanel.findElements(By.cssSelector("#mcq-statistics table"));
verifyTableBodyValues(statisticsTables.get(1), getMcqPerRecipientStatistics(question, responsesToUse, students,
instructors));
}
Expand Down Expand Up @@ -580,14 +585,28 @@ public void verifyCommentDetails(WebElement commentSection, String commentGiver,

private void verifyNoResponsesMessage(WebElement panel, boolean isQuestion, boolean isGiver) {
WebElement noResponsesMessage = panel.findElement(By.id("no-responses"));
String actualText = getElementTextContent(noResponsesMessage);
if (isQuestion) {
assertEquals("There are no responses for this question or you may not have the permission to"
+ " see the response.", noResponsesMessage.getText());
+ " see the response.", actualText);
} else {
assertEquals("There are no responses " + (isGiver ? "given" : "received")
+ " by this user or you may not have the permission to see the responses.",
noResponsesMessage.getText());
actualText);
}
}

/**
* Text as shown in the DOM (uses textContent when getText() is empty — e.g. Firefox + italic-only message).
*/
private String getElementTextContent(WebElement element) {
String text = element.getText();
if (text != null && !text.trim().isEmpty()) {
return text.trim();
}
Object content = executeScript(
"var e = arguments[0]; return (e.textContent || e.innerText || '').trim();", element);
return content == null ? "" : content.toString();
}

// Methods for formatting expected results
Expand Down Expand Up @@ -882,7 +901,8 @@ private WebElement getQuestionPanel(WebElement parentPanel, int qnNum) {
private WebElement getSectionPanel(String sectionName) {
List<WebElement> sectionPanels = browser.driver.findElements(By.id("section-panel"));
for (WebElement sectionPanel : sectionPanels) {
if (sectionPanel.getText().startsWith(sectionName)) {
String headerText = sectionPanel.findElement(By.id("section-header")).getText().trim();
if (headerText.startsWith(sectionName)) {
return sectionPanel;
}
}
Expand All @@ -895,7 +915,8 @@ private WebElement getTeamPanel(WebElement sectionPanel, String teamName) {
for (int attempt = 0; attempt < maxAttempts; attempt++) {
List<WebElement> teamPanels = sectionPanel.findElements(By.id("team-panel"));
for (WebElement teamPanel : teamPanels) {
if (teamPanel.getText().startsWith(teamName)) {
String headerText = teamPanel.findElement(By.id("team-header")).getText().trim();
if (headerText.startsWith(teamName)) {
return teamPanel;
}
}
Expand All @@ -909,7 +930,8 @@ private WebElement getTeamPanel(WebElement sectionPanel, String teamName) {
private WebElement getUserPanel(WebElement parentPanel, String header) {
List<WebElement> userPanels = parentPanel.findElements(By.id("user-panel"));
for (WebElement userPanel : userPanels) {
if (userPanel.getText().startsWith(header)) {
String headerText = userPanel.findElement(By.id("user-header")).getText().trim().replace("\n", " ");
if (headerText.startsWith(header)) {
return userPanel;
}
}
Expand Down Expand Up @@ -1011,7 +1033,8 @@ private WebElement getGroupedResponses(WebElement userPanel, String userName, St
continue;
}
String usersDetails = groupedResponse.findElement(By.id("users-details")).getText();
if (usersDetails.startsWith(expectedStarting)) {
// Match first line after normalizing whitespace (DOM may break name and team across lines).
if (normalizeSingleLine(usersDetails).startsWith(normalizeSingleLine(expectedStarting))) {
return groupedResponse;
}
}
Expand All @@ -1021,6 +1044,10 @@ private WebElement getGroupedResponses(WebElement userPanel, String userName, St
throw new NoSuchElementException("Grouped responses not found for " + userName);
}

private static String normalizeSingleLine(String s) {
return s.replaceAll("\\s+", " ").trim();
}

private WebElement getTeamStats(WebElement parentPanel, int qnNum) {
List<WebElement> teamStats = parentPanel.findElements(By.id("team-statistics"));

Expand Down Expand Up @@ -1101,7 +1128,7 @@ private String getSection(FeedbackParticipantType type, String participant, Coll
.map(Student::getSectionName)
.orElse(null);
} else if (type == FeedbackParticipantType.INSTRUCTORS || type == FeedbackParticipantType.NONE) {
sectionName = "None";
sectionName = Const.DEFAULT_SECTION;
} else {
sectionName = students.stream()
.filter(student -> student.getEmail().equals(participant))
Expand All @@ -1112,7 +1139,7 @@ private String getSection(FeedbackParticipantType type, String participant, Coll
if (sectionName == null) {
throw new RuntimeException("cannot find section name for " + participant);
}
if ("None".equals(sectionName)) {
if (Const.DEFAULT_SECTION.equals(sectionName)) {
sectionName = NO_SECTION_LABEL;
}
return sectionName;
Expand All @@ -1124,7 +1151,7 @@ private String getTeam(FeedbackParticipantType type, String participant, Collect
} else if (type == FeedbackParticipantType.TEAMS) {
return participant;
} else if (type == FeedbackParticipantType.INSTRUCTORS) {
return "Instructors";
return INSTRUCTOR_TEAM_DISPLAY_LABEL;
}
String teamName = students.stream()
.filter(student -> student.getEmail().equals(participant))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;

import teammates.common.util.Const;
import teammates.common.util.TimeHelper;
import teammates.storage.sqlentity.DeadlineExtension;
import teammates.storage.sqlentity.FeedbackSession;
Expand Down Expand Up @@ -127,7 +128,7 @@ private void verifyStudentDeadlines(Map<String, String> studentDeadlines,
Student expectedStudent = students.get(email);

if (expectedStudent.getSectionName() == null || expectedStudent.getSectionName().isEmpty()) {
assertEquals("None", section);
assertEquals(Const.DEFAULT_SECTION, section);
} else {
assertEquals(expectedStudent.getSectionName(), section);
}
Expand Down
6 changes: 3 additions & 3 deletions src/e2e/resources/data/AdminAccountsPageE2ETestSql.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,21 +200,21 @@
"course": {
"id": "tm.e2e.AAccounts.CS2104"
},
"name": "None"
"name": "__TEAMMATES_RESERVED_DEFAULT_SECTION__"
},
"CS2103None": {
"id": "00000000-0000-4000-8000-000000000102",
"course": {
"id": "tm.e2e.AAccounts.CS2103"
},
"name": "None"
"name": "__TEAMMATES_RESERVED_DEFAULT_SECTION__"
},
"CS1101None": {
"id": "00000000-0000-4000-8000-000000000103",
"course": {
"id": "tm.e2e.AAccounts.CS1101"
},
"name": "None"
"name": "__TEAMMATES_RESERVED_DEFAULT_SECTION__"
}
},
"teams": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,14 @@
"course": {
"id": "tm.e2e.FCSumOptQn.CS2104"
},
"name": "None"
"name": "__TEAMMATES_RESERVED_DEFAULT_SECTION__"
},
"ProgrammingMethodologyNone": {
"id": "00000000-0000-4000-8000-000000000102",
"course": {
"id": "tm.e2e.FCSumOptQn.CS1101"
},
"name": "None"
"name": "__TEAMMATES_RESERVED_DEFAULT_SECTION__"
}
},
"teams": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,14 @@
"course": {
"id": "tm.e2e.FCSumRcptQn.CS2104"
},
"name": "None"
"name": "__TEAMMATES_RESERVED_DEFAULT_SECTION__"
},
"ProgrammingMethodologyNone": {
"id": "00000000-0000-4000-8000-000000000102",
"course": {
"id": "tm.e2e.FCSumRcptQn.CS1101"
},
"name": "None"
"name": "__TEAMMATES_RESERVED_DEFAULT_SECTION__"
}
},
"teams": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,14 @@
"course": {
"id": "tm.e2e.FContrQn.CS2104"
},
"name": "None"
"name": "__TEAMMATES_RESERVED_DEFAULT_SECTION__"
},
"CS1101None": {
"id": "00000000-0000-4000-8000-000000000102",
"course": {
"id": "tm.e2e.FContrQn.CS1101"
},
"name": "None"
"name": "__TEAMMATES_RESERVED_DEFAULT_SECTION__"
}
},
"teams": {
Expand Down
4 changes: 2 additions & 2 deletions src/e2e/resources/data/FeedbackMsqQuestionE2ESqlTest.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,14 @@
"course": {
"id": "tm.e2e.FMsqQn.CS2104"
},
"name": "None"
"name": "__TEAMMATES_RESERVED_DEFAULT_SECTION__"
},
"ProgrammingMethodologysNone": {
"id": "00000000-0000-4000-8000-000000000102",
"course": {
"id": "tm.e2e.FMsqQn.CS1101"
},
"name": "None"
"name": "__TEAMMATES_RESERVED_DEFAULT_SECTION__"
}
},
"teams": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@
"course": {
"id": "tm.e2e.FNumScaleQn.CS2104"
},
"name": "None"
"name": "__TEAMMATES_RESERVED_DEFAULT_SECTION__"
},
"ProgrammingMethodologyNone": {
"id": "00000000-0000-4000-8000-000000000102",
"course": {
"id": "tm.e2e.FNumScaleQn.CS1101"
},
"name": "None"
"name": "__TEAMMATES_RESERVED_DEFAULT_SECTION__"
}
},
"teams": {
Expand Down
Loading
Loading