Skip to content

Commit f83714d

Browse files
committed
test(teach): add desktop tests for all /education/ pages
1 parent 2092ae7 commit f83714d

21 files changed

+766
-7
lines changed

static/js/ktl-component/courses/index.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {TeachMap} from '../teach/components/teach-map/teach-map.jsx';
1212
const Courses = ({universities, path}) => {
1313
const [activeIndex, setActiveIndex] = useState(0);
1414
return (
15-
<div>
15+
<div data-test="teach-courses">
1616
<TeachTopMenu path={path} />
1717

1818
<section className="ktl-layout ktl-layout--center">

static/js/ktl-component/why-teach/index.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import quotes from './quotes.json';
1313
export const WhyTeach = ({path}) => {
1414

1515
return (
16-
<div className="teach-wrapper">
16+
<div className="teach-wrapper" data-test="teach-why-teach-page">
1717
<TeachTopMenu path={path}/>
1818

1919
<div className="ktl-layout ktl-layout--center">

test/e2e/teach/courses.spec.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { expect, test } from '@playwright/test';
2+
import { CoursesPage } from '../../page/teach/courses-page';
3+
import { closeExternalBanners } from '../utils';
4+
import { testSelector } from '../../utils';
5+
import { checkTeachCta, checkTeachMap, checkTeachNav } from './utils';
6+
7+
test.describe('Courses page appearance and functionality', async () => {
8+
test.beforeEach(async ({ page, context, baseURL }) => {
9+
const coursesPage = new CoursesPage(page);
10+
await coursesPage.init();
11+
await closeExternalBanners(context, page, baseURL);
12+
});
13+
14+
test('Should load the courses page correctly', async ({ page }) => {
15+
// Check if the page title is correct
16+
expect(await page.title()).toBe('List of Courses');
17+
18+
// Check if the main content is visible
19+
const mainContent = page.locator(testSelector('teach-courses'));
20+
await expect(mainContent).toBeVisible();
21+
22+
// Check if the page heading is correct
23+
const title = mainContent.locator('h1');
24+
await expect(title).toBeVisible();
25+
26+
expect(await title.textContent()).toBe('Universities That Teach Kotlin');
27+
});
28+
29+
test('Should have working navigation buttons', async ({ page }) => {
30+
await checkTeachNav(page, 'List of Courses');
31+
});
32+
33+
test('Should have working tab navigation', async ({ page }) => {
34+
// Check if the tab list is visible
35+
const tabList = page.locator(testSelector('tab-list'));
36+
await expect(tabList).toBeVisible();
37+
38+
// Check if both tabs are present
39+
const tableViewTab = tabList.getByRole('tab', { name: 'Table view', exact: true });
40+
await expect(tableViewTab).toBeVisible();
41+
42+
const mapViewTab = tabList.getByRole('tab', { name: 'Map view', exact: true });
43+
await expect(mapViewTab).toBeVisible();
44+
45+
await expect(tableViewTab).toHaveAttribute('data-test', 'tab tab-selected');
46+
await expect(mapViewTab).toHaveAttribute('data-test', 'tab');
47+
48+
// CoursesList should be visible in the table view
49+
await expect(page.locator('.ktl-courses-list')).toBeVisible();
50+
51+
// Switch to the map view
52+
await mapViewTab.click();
53+
54+
// Map view should now be active
55+
await expect(tableViewTab).toHaveAttribute('data-test', 'tab');
56+
await expect(mapViewTab).toHaveAttribute('data-test', 'tab tab-selected');
57+
58+
// TeachMap should be visible in the map view
59+
await expect(page.locator('.teach-map .gm-style')).toBeVisible();
60+
61+
// Switch back to the table view
62+
await tableViewTab.click();
63+
64+
// Table view should be active again
65+
await expect(tableViewTab).toHaveAttribute('data-test', 'tab tab-selected');
66+
await expect(mapViewTab).toHaveAttribute('data-test', 'tab');
67+
68+
// CoursesList should be visible again
69+
await expect(page.locator('.ktl-courses-list')).toBeVisible();
70+
});
71+
72+
test('Should display university list in table view', async ({ page }) => {
73+
// Make sure we're in a table view
74+
const tableViewTab = page.getByRole('tab', { name: 'Table view' });
75+
await tableViewTab.click();
76+
77+
// Check if the course list is visible
78+
const coursesList = page.locator('.ktl-courses-list');
79+
await expect(coursesList).toBeVisible();
80+
81+
// Check and verify the headers of the course list
82+
const headers = coursesList.locator('.ktl-courses-list-header .ktl-courses-list-cell');
83+
expect(await headers.count()).toBe(3);
84+
expect(await headers.nth(0).textContent()).toBe('University title');
85+
expect(await headers.nth(1).textContent()).toBe('Location');
86+
expect(await headers.nth(2).textContent()).toBe('Teaching Kotlin');
87+
88+
// Check if there are universities in the list
89+
const universities = coursesList.locator('.ktl-courses-list__item');
90+
expect(await universities.count()).toBeGreaterThan(0);
91+
expect(await universities.first().locator('.ktl-courses-list-cell').count())
92+
.toBe(3);
93+
});
94+
95+
test('Should display map with markers in map view', async ({ page }) => {
96+
// Switch to the map view
97+
const mapViewTab = page.getByRole('tab', { name: 'Map view' });
98+
await mapViewTab.click();
99+
100+
// Check if the map is visible
101+
const map = page.locator('.teach-map');
102+
await checkTeachMap(page, map);
103+
});
104+
105+
test('Should have action buttons for educators', checkTeachCta);
106+
});

test/e2e/teach/education.spec.ts

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
import { expect, test } from '@playwright/test';
2+
import { TeachPage } from '../../page/teach/education';
3+
import { closeExternalBanners } from '../utils';
4+
import { testSelector } from '../../utils';
5+
import { checkTeachCta, checkTeachMap, checkTeachNav, MAILTO_LINK, MATERIALS_LINK, SIGNUP_LINK } from './utils';
6+
7+
test.describe('Education landing page content and interactions', async () => {
8+
test.beforeEach(async ({ context, page, baseURL }) => {
9+
const teachPage = new TeachPage(page);
10+
await teachPage.init();
11+
await closeExternalBanners(context, page, baseURL);
12+
});
13+
14+
test('Should load the education page correctly', async ({ page }) => {
15+
// Check if the page title is correct
16+
expect(await page.title()).toBe('Kotlin for Education');
17+
18+
// Check if the main content is visible
19+
const mainContent = page.locator(testSelector('teach-index-page'));
20+
await expect(mainContent).toBeVisible();
21+
22+
// Check if the page heading is correct
23+
const title = mainContent.locator('h1');
24+
await expect(title).toBeVisible();
25+
26+
expect(await title.textContent()).toBe('Teach Computer Science with Kotlin');
27+
});
28+
29+
test('Should have working navigation buttons', async ({ page }) => {
30+
await checkTeachNav(page, 'Overview');
31+
});
32+
33+
test('Should display course materials download button', async ({ page }) => {
34+
const block = page.locator('.teach-launch-course__text');
35+
36+
// Locate and verify the download button with the exact name match
37+
const button = block.getByRole('link', { name: 'Download all materials →', exact: true });
38+
await expect(button).toBeVisible();
39+
await expect(button).toHaveAttribute('href', MATERIALS_LINK);
40+
41+
expect(await block.screenshot()).toMatchSnapshot('launch-course-text.png');
42+
});
43+
44+
test('Should display features section with features', async ({ page }) => {
45+
// Check if the features section is visible
46+
const featuresSection = page.locator('.teach-features');
47+
await expect(featuresSection).toBeVisible();
48+
49+
// Check if there are exactly 3 features
50+
const expectedFeatures = ['Academically recognized', 'Language of the industry', 'Multiplatform'];
51+
const features = featuresSection.locator('.teach-feature');
52+
expect(await features.count()).toBe(3);
53+
54+
// Check each feature
55+
for (let i = 0; i < expectedFeatures.length; i++) {
56+
const feature = features.nth(i);
57+
await expect(feature.locator('.teach-feature__icon img')).toBeVisible();
58+
expect(await feature.locator('.ktl-h3').textContent()).toBe(expectedFeatures[i]);
59+
}
60+
61+
expect(await featuresSection.screenshot()).toMatchSnapshot('teach-features.png');
62+
});
63+
64+
test('Should display buttons in top section', async ({ page }) => {
65+
// Get the top buttons block container
66+
const block = page.locator('.teach-top-buttons');
67+
68+
// Check the "Join Educators Community" button visibility and link
69+
const join = block.getByRole('link', { name: 'Join Educators Сommunity', exact: true });
70+
await expect(join).toBeVisible();
71+
await expect(join).toHaveAttribute('href', SIGNUP_LINK);
72+
73+
// Check the "Why Teach Kotlin" button visibility and link
74+
const why = block.getByRole('link', { name: 'Why Teach Kotlin →', exact: true });
75+
await expect(why).toBeVisible();
76+
await expect(why).toHaveAttribute('href', 'why-teach-kotlin.html');
77+
78+
expect(await block.screenshot()).toMatchSnapshot('teach-top-mobile-buttons.png');
79+
});
80+
81+
test('Should display university statistics correctly', async ({ page }) => {
82+
// Check if the university count is displayed
83+
const universitiesText = page.locator('.universities-top__title h2');
84+
expect(await universitiesText.textContent()).toBe('Kotlin Courses Around the World');
85+
86+
// Check if the TeachNumbers component is visible
87+
const teachNumbers = page.locator('.universities-top__numbers');
88+
await expect(teachNumbers).toBeVisible();
89+
90+
// Check if the TeachNumbers component is visible
91+
const subtitles = teachNumbers.locator('.teach-number__subtitle');
92+
expect(await subtitles.count()).toBe(2);
93+
expect(await subtitles.nth(0).textContent()).toBe('countries');
94+
expect(await subtitles.nth(1).textContent()).toBe('universities');
95+
});
96+
97+
test('Should display university logos', async ({ page }) => {
98+
// Check if the university logos are visible
99+
const logos = page.locator('.teach-logos__logo');
100+
expect(await logos.count()).toBe(5);
101+
102+
// Check specific universities
103+
await expect(page.locator('img[alt="Harvard University"]')).toBeVisible();
104+
await expect(page.locator('img[alt="University of Cambridge"]')).toBeVisible();
105+
await expect(page.locator('img[alt="Stanford University"]')).toBeVisible();
106+
await expect(page.locator('img[alt="Imperial College London"]')).toBeVisible();
107+
await expect(page.locator('img[alt="The University of Chicago"]')).toBeVisible();
108+
109+
expect(await page.locator('.teach-logos').screenshot()).toMatchSnapshot('teach-logos.png');
110+
});
111+
112+
test('Should have a working interactive map', async ({ page }) => {
113+
// Check if the map is visible
114+
const map = page.locator('.teach-map__wrapper');
115+
await checkTeachMap(page, map);
116+
});
117+
118+
test('Should display navigation buttons', async ({ page }) => {
119+
const bottom = page.locator('.teach-universities__bottom');
120+
await expect(bottom).toBeVisible();
121+
122+
// Check if the mailto button is visible and working
123+
const mailtoButton = bottom.getByRole('link', { name: '[email protected].', exact: true });
124+
await expect(mailtoButton).toBeVisible();
125+
await expect(mailtoButton).toHaveAttribute('href', MAILTO_LINK);
126+
127+
// Check if the "All Universities" button is visible
128+
const allUniversitiesButton = bottom.getByRole('link', { name: 'All universities', exact: true });
129+
await expect(allUniversitiesButton).toBeVisible();
130+
await expect(allUniversitiesButton).toHaveAttribute('href', 'courses.html');
131+
});
132+
133+
test('Should have comprehensive resource links section', async ({ page }) => {
134+
// Check if the resources section is visible
135+
const resourcesSection = page.locator('#start-teaching-kotlin');
136+
await expect(resourcesSection).toBeVisible();
137+
138+
// Check section title
139+
const sectionTitle = resourcesSection.locator('h2');
140+
await expect(sectionTitle).toHaveText('Start Teaching Kotlin with These Resources');
141+
142+
// Check category headings
143+
const expectedTitles = ['Get started', 'Tools', 'Online Courses', 'Android in Kotlin', 'Practice Kotlin'];
144+
const categoryHeadings = resourcesSection.locator('.ktl-h4');
145+
await expect(categoryHeadings).toHaveCount(expectedTitles.length);
146+
147+
// Check each category heading and its associated links
148+
for (let i = 0; i < expectedTitles.length; i++) {
149+
const item = categoryHeadings.nth(i);
150+
await expect(item, `Category heading should be ${expectedTitles[i]}`).toHaveText(expectedTitles[i]);
151+
const links = item.locator(':scope + .teach-list > li');
152+
expect(await links.count(), `${expectedTitles[i]} category should have at least one link`).toBeGreaterThan(0);
153+
}
154+
});
155+
156+
test('Should have a working subscription form', async ({ page }) => {
157+
const email = '[email protected]';
158+
159+
await page.route('https://forms-service.jetbrains.com/marketo', route => {
160+
if (route.request().method() !== 'POST') route.continue();
161+
route.fulfill({
162+
status: 200,
163+
contentType: 'application/json',
164+
body: JSON.stringify({ 'success': true, 'cause': [] })
165+
});
166+
});
167+
168+
await page.route(`https://account.jetbrains.com/services/acceptAgreement?email=${encodeURIComponent(email)}&type=mkt.newsletter.visitor`, route => {
169+
if (route.request().method() !== 'POST') route.continue();
170+
route.fulfill({
171+
status: 200,
172+
contentType: 'application/json',
173+
body: JSON.stringify({ 'status': 'OK' })
174+
});
175+
});
176+
177+
// Locate the subscription form
178+
const subscriptionForm = page.locator('.teach-subscription-section');
179+
await expect(subscriptionForm).toBeVisible();
180+
181+
// Find the form elements
182+
await subscriptionForm.locator('input[name="Email"]').fill(email);
183+
184+
// Check the privacy checkbox by clicking its label
185+
await subscriptionForm.locator('.teach-subscription-form__checkbox label').click();
186+
187+
// Submit the form
188+
await subscriptionForm.locator('button[type="submit"]').click();
189+
190+
// Verify that the form shows the submitted state (check icon appears)
191+
await expect(subscriptionForm.locator('.teach-subscription-form__submitted-icon')).toBeVisible();
192+
193+
expect(await subscriptionForm.screenshot()).toMatchSnapshot('subscription-form.png');
194+
});
195+
196+
test('Should have a working YouTube player', async ({ page }) => {
197+
// Check if the YouTube player container is visible
198+
const youtubePlayer = page.locator('.teach-video');
199+
await expect(youtubePlayer).toBeVisible();
200+
201+
// Check if the player is in "show video" mode
202+
await expect(youtubePlayer.locator('[class*="ktl-youtube-player-module_preview_"]')).toBeVisible();
203+
204+
// Check if the play button is visible
205+
const playButton = youtubePlayer.locator('[class*="ktl-youtube-player-module_play-button_"]');
206+
await expect(playButton).toBeVisible();
207+
208+
// Click the play button to start the video
209+
await playButton.click();
210+
211+
// After clicking, the play button should be hidden and the video should be playing
212+
await expect(playButton).toBeHidden();
213+
214+
// Check if the iframe is loaded correctly
215+
const iframe = youtubePlayer.locator('iframe');
216+
await expect(iframe).toBeVisible();
217+
218+
// Check if the iframe has the correct src attribute (YouTube embed URL)
219+
const iframeSrc = await iframe.getAttribute('src');
220+
expect(iframeSrc).toBeTruthy();
221+
expect(iframeSrc).toContain('youtube.com');
222+
expect(iframeSrc).toContain('PLlFc5cFwUnmzT4cgLOGJYGnY6j0W2xoFA');
223+
});
224+
225+
test('Should have working quotes slider', async ({ page }) => {
226+
const quotes = page.locator('[class*=ktl-quotes-slider-module_quotes-slider_]');
227+
228+
// Initial state of quotes
229+
const defaultAuthor = quotes.getByText('David Vaughn', { exact: false });
230+
await expect(defaultAuthor).toBeVisible();
231+
232+
const quoteContent = quotes.getByText('Kotlin is faster to develop', { exact: false });
233+
await expect(quoteContent).toBeVisible();
234+
235+
// Controls of navigation
236+
const controls = page.locator('[class*=ktl-quotes-slider-module_control_]');
237+
await expect(controls).toHaveCount(2);
238+
239+
const backButton = controls.first();
240+
await expect(backButton).not.toHaveClass(/ktl-quotes-slider-module_control-active_/);
241+
242+
const forwardButton = controls.last();
243+
await expect(forwardButton).toHaveClass(/ktl-quotes-slider-module_control-active_/);
244+
245+
await forwardButton.click();
246+
247+
await expect(quotes.getByText('Sergey Bratus')).toBeVisible();
248+
await expect(quotes.getByText('I used Kotlin')).toBeVisible();
249+
250+
await expect(backButton).toHaveClass(/ktl-quotes-slider-module_control-active_/);
251+
});
252+
253+
test('Should have action buttons for educators', checkTeachCta);
254+
});

0 commit comments

Comments
 (0)