blob: e27fb54953c13ab7f750dbd93052cda7d13dcb37 [file] [log] [blame]
Philipp Schrader175a93c2023-02-19 13:13:40 -08001/// <reference types="cypress" />
2
3function disableAlerts() {
4 cy.get('#block_alerts').check({force: true}).should('be.checked');
5}
6
7function switchToTab(tabName) {
8 cy.contains('.nav-link', tabName).click();
9}
10
11function headerShouldBe(text) {
12 cy.get('.header').should('have.text', text);
13}
14
15function clickButton(buttonName) {
16 cy.contains('button', buttonName).click();
17}
18
19function setInputTo(fieldSelector, value) {
20 cy.get(fieldSelector).type('{selectAll}' + value);
21}
22
23// Moves the nth slider left or right. A positive "adjustBy" value moves the
24// slider to the right. A negative value moves the slider to the left.
25//
26// negative/left <--- 0 ---> positive/right
27function adjustNthSliderBy(n, adjustBy) {
28 let element = cy.get('input[type=range]').eq(n);
29 element.scrollIntoView();
30 element.invoke('val').then((currentValue) => {
31 // We need to query for the slider here again because `invoke('val')` above
32 // somehow invalidates further calls to `val`.
33 cy.get('input[type=range]')
34 .eq(n)
35 .invoke('val', currentValue + adjustBy)
36 .trigger('change');
37 });
38}
39
40// Asserts that the field on the "Submit and Review" screen has a specific
41// value.
42function expectReviewFieldToBe(fieldName, expectedValue) {
43 expectNthReviewFieldToBe(fieldName, 0, expectedValue);
44}
45
46// Asserts that the n'th instance of a field on the "Submit and Review"
47// screen has a specific value.
48function expectNthReviewFieldToBe(fieldName, n, expectedValue) {
49 getNthReviewField(fieldName, n).should(
50 'have.text',
51 `${fieldName}: ${expectedValue}`
52 );
53}
54
55function getNthReviewField(fieldName, n) {
56 let element = cy.get('li').filter(`:contains("${fieldName}: ")`).eq(n);
57 element.scrollIntoView();
58 return element;
59}
60
61before(() => {
62 cy.visit('/');
63 disableAlerts();
64 cy.title().should('eq', 'FRC971 Scouting Application');
65
66 // Import the match list before running any tests. Ideally this should be
67 // run in beforeEach(), but it's not worth doing that at this time. Our
68 // tests are basic enough not to require this.
69 switchToTab('Import Match List');
70 headerShouldBe('Import Match List');
71 setInputTo('#year', '2016');
72 setInputTo('#event_code', 'nytr');
73 clickButton('Import');
74
75 cy.get('.progress_message').contains('Successfully imported match list.');
76});
77
78beforeEach(() => {
79 cy.visit('/');
80 disableAlerts();
81});
82
83describe('Scouting app tests', () => {
84 it('should: show matches in chronological order.', () => {
85 headerShouldBe('Matches');
86 cy.get('.badge').eq(0).contains('Quals Match 1');
87 cy.get('.badge').eq(1).contains('Quals Match 2');
88 cy.get('.badge').eq(2).contains('Quals Match 3');
89 cy.get('.badge').eq(9).contains('Quals Match 10');
90 cy.get('.badge').eq(72).contains('Quarter Final 1 Match 1');
91 cy.get('.badge').eq(73).contains('Quarter Final 2 Match 1');
92 cy.get('.badge').eq(74).contains('Quarter Final 3 Match 1');
93 cy.get('.badge').eq(75).contains('Quarter Final 4 Match 1');
94 cy.get('.badge').eq(76).contains('Quarter Final 1 Match 2');
95 cy.get('.badge').eq(82).contains('Semi Final 1 Match 1');
96 cy.get('.badge').eq(83).contains('Semi Final 2 Match 1');
97 cy.get('.badge').eq(84).contains('Semi Final 1 Match 2');
98 cy.get('.badge').eq(85).contains('Semi Final 2 Match 2');
99 cy.get('.badge').eq(89).contains('Final 1 Match 3');
100 });
101
102 it('should: prefill the match information.', () => {
103 headerShouldBe('Matches');
104
105 // On the 87th row of matches (index 86) click on the second team
106 // (index 1) which resolves to team 5254 in semi final 2 match 3.
107 cy.get('button.match-item')
108 .eq(86 * 6 + 1)
109 .click();
110
111 headerShouldBe('Team Selection');
112 cy.get('#match_number').should('have.value', '3');
113 cy.get('#team_number').should('have.value', '5254');
114 cy.get('#set_number').should('have.value', '2');
115 cy.get('#comp_level').should('have.value', '3: sf');
116 });
117
118 it('should: error on unknown match.', () => {
119 switchToTab('Data Entry');
120 headerShouldBe('Team Selection');
121
122 // Pick a match that doesn't exist in the 2016nytr match list.
123 setInputTo('#match_number', '3');
124 setInputTo('#team_number', '971');
125
126 // Click Next until we get to the submit screen.
127 for (let i = 0; i < 5; i++) {
128 clickButton('Next');
129 }
130 headerShouldBe('Review and Submit');
131
132 // Attempt to submit and validate the error.
133 clickButton('Submit');
134 cy.get('.error_message').contains(
135 'Failed to find team 971 in match 3 in the schedule.'
136 );
137 });
138
139 // Make sure that each page on the Entry tab has both "Next" and "Back"
140 // buttons. The only screens exempted from this are the first page and the
141 // last page.
142 it('should: have forwards and backwards buttons.', () => {
143 switchToTab('Data Entry');
144
145 const expectedOrder = [
146 'Team Selection',
147 'Auto',
148 'TeleOp',
149 'Climb',
150 'Other',
151 'Review and Submit',
152 ];
153
154 // Go forward through the screens.
155 for (let i = 0; i < expectedOrder.length; i++) {
156 headerShouldBe(expectedOrder[i]);
157 if (i != expectedOrder.length - 1) {
158 clickButton('Next');
159 }
160 }
161
162 // Go backwards through the screens.
163 for (let i = 0; i < expectedOrder.length; i++) {
164 headerShouldBe(expectedOrder[expectedOrder.length - i - 1]);
165 if (i != expectedOrder.length - 1) {
166 clickButton('Back');
167 }
168 }
169 });
170
171 it('should: review and submit correct data.', () => {
172 switchToTab('Data Entry');
173
174 // Submit scouting data for a random team that attended 2016nytr.
175 headerShouldBe('Team Selection');
176 setInputTo('#match_number', '2');
177 setInputTo('#team_number', '5254');
178 setInputTo('#set_number', '42');
179 cy.get('#comp_level').select('Semi Finals');
180 clickButton('Next');
181
182 headerShouldBe('Auto');
183 cy.get('#quadrant3').check();
184 clickButton('Next');
185
186 headerShouldBe('TeleOp');
187 clickButton('Next');
188
189 headerShouldBe('Climb');
190 cy.get('#high').check();
191 clickButton('Next');
192
193 headerShouldBe('Other');
194 adjustNthSliderBy(0, 3);
195 adjustNthSliderBy(1, 1);
196 cy.get('#no_show').check();
197 cy.get('#mechanically_broke').check();
198 setInputTo('#comment', 'A very useful comment here.');
199 clickButton('Next');
200
201 headerShouldBe('Review and Submit');
202 cy.get('.error_message').should('have.text', '');
203
204 // Validate Team Selection.
205 expectReviewFieldToBe('Match number', '2');
206 expectReviewFieldToBe('Team number', '5254');
207 expectReviewFieldToBe('SetNumber', '42');
208 expectReviewFieldToBe('Comp Level', 'Semi Finals');
209
210 // Validate Auto.
211 expectNthReviewFieldToBe('Upper Shots Made', 0, '0');
212 expectNthReviewFieldToBe('Lower Shots Made', 0, '0');
213 expectNthReviewFieldToBe('Missed Shots', 0, '0');
214 expectReviewFieldToBe('Quadrant', '3');
215
216 // Validate TeleOp.
217 expectNthReviewFieldToBe('Upper Shots Made', 1, '0');
218 expectNthReviewFieldToBe('Lower Shots Made', 1, '0');
219 expectNthReviewFieldToBe('Missed Shots', 1, '0');
220
221 // Validate Climb.
222 expectReviewFieldToBe('Climb Level', 'High');
223
224 // Validate Other.
225 expectReviewFieldToBe('Defense Played On Rating', '3');
226 expectReviewFieldToBe('Defense Played Rating', '1');
227 expectReviewFieldToBe('No show', 'true');
228 expectReviewFieldToBe('Never moved', 'false');
229 expectReviewFieldToBe('Battery died', 'false');
230 expectReviewFieldToBe('Broke (mechanically)', 'true');
231 expectReviewFieldToBe('Comments', 'A very useful comment here.');
232
233 clickButton('Submit');
234 headerShouldBe('Success');
235 });
236
237 it('should: load all images successfully.', () => {
238 switchToTab('Data Entry');
239
240 // Get to the Auto display with the field pictures.
241 headerShouldBe('Team Selection');
242 clickButton('Next');
243 headerShouldBe('Auto');
244
245 // We expect 2 fully loaded images for each of the orientations.
246 // 2 images for the original orientation and 2 images for the flipped orientation.
247 for (let i = 0; i < 2; i++) {
248 cy.get('img').should(($imgs) => {
249 for (const $img of $imgs) {
250 expect($img.naturalWidth).to.be.greaterThan(0);
251 }
252 });
253 clickButton('Flip');
254 }
255 });
256
257 it('should: submit note scouting for multiple teams', () => {
258 // Navigate to Notes Page.
259 switchToTab('Notes');
260 headerShouldBe('Notes');
261
262 // Add first team.
263 setInputTo('#team_number_notes', '1234');
264 clickButton('Select');
265
266 // Add note and select keyword for first team.
267 cy.get('#team-key-1').should('have.text', '1234');
268 setInputTo('#text-input-1', 'Good Driving');
269 cy.get('#good_driving_0').click();
270
271 // Navigate to add team selection and add another team.
272 clickButton('Add team');
273 setInputTo('#team_number_notes', '1235');
274 clickButton('Select');
275
276 // Add note and select keyword for second team.
277 cy.get('#team-key-2').should('have.text', '1235');
278 setInputTo('#text-input-2', 'Bad Driving');
279 cy.get('#bad_driving_1').click();
280
281 // Submit Notes.
282 clickButton('Submit');
283 cy.get('#team_number_label').should('have.text', ' Team Number ');
284 });
285
286 it('should: switch note text boxes with keyboard shortcuts', () => {
287 // Navigate to Notes Page.
288 switchToTab('Notes');
289 headerShouldBe('Notes');
290
291 // Add first team.
292 setInputTo('#team_number_notes', '1234');
293 clickButton('Select');
294
295 // Add second team.
296 clickButton('Add team');
297 setInputTo('#team_number_notes', '1235');
298 clickButton('Select');
299
300 // Add third team.
301 clickButton('Add team');
302 setInputTo('#team_number_notes', '1236');
303 clickButton('Select');
304
305 for (let i = 1; i <= 3; i++) {
306 // Press Control + i
307 cy.get('body').type(`{ctrl}${i}`);
308
309 // Expect text input to be focused.
310 cy.focused().then(($element) => {
311 expect($element).to.have.id(`text-input-${i}`);
312 });
313 }
314 });
315
316 it('should: submit driver ranking', () => {
317 // Navigate to Driver Ranking Page.
318 switchToTab('Driver Ranking');
319 headerShouldBe('Driver Ranking');
320
321 // Input match and team numbers.
322 setInputTo('#match_number_selection', '11');
323 setInputTo('#team_input_0', '123');
324 setInputTo('#team_input_1', '456');
325 setInputTo('#team_input_2', '789');
326 clickButton('Select');
327
328 // Verify match and team key input.
329 cy.get('#match_number_heading').should('have.text', 'Match #11');
330 cy.get('#team_key_label_0').should('have.text', ' 123 ');
331 cy.get('#team_key_label_1').should('have.text', ' 456 ');
332 cy.get('#team_key_label_2').should('have.text', ' 789 ');
333
334 // Rank teams.
335 cy.get('#up_button_2').click();
336 cy.get('#down_button_0').click();
337
338 // Verify ranking change.
339 cy.get('#team_key_label_0').should('have.text', ' 789 ');
340 cy.get('#team_key_label_1').should('have.text', ' 123 ');
341 cy.get('#team_key_label_2').should('have.text', ' 456 ');
342
343 // Submit.
344 clickButton('Submit');
345 });
346});