Skip to content

Commit fca7373

Browse files
committed
test(teach): add desktop tests for all /education/ pages
1 parent 6b2c915 commit fca7373

21 files changed

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

0 commit comments

Comments
 (0)