Skip to content

Commit 54e3727

Browse files
authored
Fix part of oppia#21646: Added CUJ 4.1 - See statistics and previous explorations on the creator dashboard (oppia#22042)
* Fix part of oppia#21646: Added CUJ 4.1 * Resolve comments and fixed test-duration * fix: removed flag from save exploration function
1 parent b3342cb commit 54e3727

File tree

5 files changed

+251
-3
lines changed

5 files changed

+251
-3
lines changed

core/templates/pages/creator-dashboard-page/creator-dashboard-page.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ <h1 tabindex="0" [innerHTML]="'I18N_DASHBOARD_CREATOR_DASHBOARD' | translate"></
2323
[hidden]="dashboardStats.numRatings || relativeChangeInTotalPlays">
2424
{{ dashboardStats.averageRatings || 'N/A' }}
2525
</h2>
26-
<div [hidden]="!dashboardStats.numRatings">
26+
<div class="e2e-test-oppia-total-users" [hidden]="!dashboardStats.numRatings">
2727
<p *ngIf="dashboardStats.numRatings === 1">
2828
(by {{ dashboardStats.numRatings }} user)
2929
</p>

core/tests/ci-test-suite-configs/acceptance.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@
4848
"name": "exploration-editor/save-draft-publish-and-discard-the-changes",
4949
"module": "core/tests/puppeteer-acceptance-tests/specs/exploration-editor/save-draft-publish-and-discard-the-changes.spec.ts"
5050
},
51+
{
52+
"name": "exploration-editor/verify-statistics-and-previous-explorations",
53+
"module": "core/tests/puppeteer-acceptance-tests/specs/exploration-editor/verify-statistics-and-previous-explorations.spec.ts"
54+
},
5155
{
5256
"name": "logged-in-user/create-and-delete-account",
5357
"module": "core/tests/puppeteer-acceptance-tests/specs/logged-in-user/create-and-delete-account.spec.ts"
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright 2025 The Oppia Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS-IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/**
16+
* @fileoverview Acceptance tests for viewing statistics and past explorations on the creator dashboard.
17+
* User Journey: Create explorations, have another user play and rate one, then verify stats as the creator.
18+
*/
19+
20+
import {UserFactory} from '../../utilities/common/user-factory';
21+
import testConstants from '../../utilities/common/test-constants';
22+
import {LoggedOutUser} from '../../utilities/user/logged-out-user';
23+
import {LoggedInUser} from '../../utilities/user/logged-in-user';
24+
import {ExplorationEditor} from '../../utilities/user/exploration-editor';
25+
26+
const DEFAULT_SPEC_TIMEOUT_MSECS = testConstants.DEFAULT_SPEC_TIMEOUT_MSECS;
27+
28+
describe('Exploration Editor', function () {
29+
let explorationEditor: ExplorationEditor;
30+
let loggedInUser: LoggedInUser & LoggedOutUser;
31+
32+
beforeAll(async function () {
33+
explorationEditor = await UserFactory.createNewUser(
34+
'explorationEditor',
35+
'exploration_editor@example.com'
36+
);
37+
38+
await explorationEditor.createAndPublishAMinimalExplorationWithTitle(
39+
'Rational Numbers'
40+
);
41+
await explorationEditor.createAndPublishAMinimalExplorationWithTitle(
42+
'Real Numbers',
43+
'Algebra',
44+
false
45+
);
46+
await explorationEditor.createAndPublishAMinimalExplorationWithTitle(
47+
'Fractions',
48+
'Algebra',
49+
false
50+
);
51+
// These explorations are not published but are saved as drafts.
52+
await explorationEditor.createAndSaveAMinimalExploration();
53+
await explorationEditor.createAndSaveAMinimalExploration();
54+
55+
loggedInUser = await UserFactory.createNewUser(
56+
'loggedInUser',
57+
'logged_in_user@example.com'
58+
);
59+
}, DEFAULT_SPEC_TIMEOUT_MSECS);
60+
61+
it(
62+
'should display created explorations and their statistics on the creator dashboard after creating, playing, and rating as a logged-in user',
63+
async function () {
64+
await loggedInUser.navigateToCommunityLibraryPage();
65+
await loggedInUser.searchForLessonInSearchBar('Rational Numbers');
66+
await loggedInUser.playLessonFromSearchResults('Rational Numbers');
67+
await loggedInUser.expectExplorationCompletionToastMessage(
68+
'Congratulations for completing this lesson!'
69+
);
70+
await loggedInUser.rateExploration(3, 'Nice!', false);
71+
72+
await explorationEditor.navigateToCreatorDashboardPage();
73+
await explorationEditor.expectAverageRatingAndUsersToBe(3, 1);
74+
await explorationEditor.expectTotalPlaysToBe(1);
75+
await explorationEditor.expectOpenFeedbacksToBe(1);
76+
await explorationEditor.expectNumberOfSubscribersToBe(0);
77+
await explorationEditor.expectNumberOfExplorationsToBe(5);
78+
await explorationEditor.expectExplorationNameToAppearNTimes(
79+
'Rational Numbers'
80+
);
81+
await explorationEditor.expectExplorationNameToAppearNTimes(
82+
'Real Numbers'
83+
);
84+
await explorationEditor.expectExplorationNameToAppearNTimes('Fractions');
85+
await explorationEditor.expectExplorationNameToAppearNTimes(
86+
'Untitled',
87+
2
88+
);
89+
},
90+
DEFAULT_SPEC_TIMEOUT_MSECS
91+
);
92+
93+
afterAll(async function () {
94+
await UserFactory.closeAllBrowsers();
95+
});
96+
});

core/tests/puppeteer-acceptance-tests/utilities/user/exploration-editor.ts

Lines changed: 149 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,10 @@ const stayAnonymousCheckbox = '.e2e-test-stay-anonymous-checkbox';
219219
const responseTextareaSelector = '.e2e-test-feedback-response-textarea';
220220
const sendButtonSelector = '.e2e-test-oppia-feedback-response-send-btn';
221221
const errorSavingExplorationModal = '.e2e-test-discard-lost-changes-button';
222+
const totalPlaysSelector = '.e2e-test-oppia-total-plays';
223+
const numberOfOpenFeedbacksSelector = '.e2e-test-oppia-open-feedback';
224+
const avarageRatingSelector = '.e2e-test-oppia-average-rating';
225+
const usersCountInRatingSelector = '.e2e-test-oppia-total-users';
222226

223227
const LABEL_FOR_SAVE_DESTINATION_BUTTON = ' Save Destination ';
224228
export class ExplorationEditor extends BaseUser {
@@ -1706,14 +1710,18 @@ export class ExplorationEditor extends BaseUser {
17061710

17071711
/**
17081712
* Function for creating an exploration with only EndExploration interaction with given title.
1713+
* @param {boolean} flag - Determines whether to dismiss the welcome modal.
17091714
*/
17101715
async createAndPublishAMinimalExplorationWithTitle(
17111716
title: string,
1712-
category: string = 'Algebra'
1717+
category: string = 'Algebra',
1718+
flag: boolean = true
17131719
): Promise<string | null> {
17141720
await this.navigateToCreatorDashboardPage();
17151721
await this.navigateToExplorationEditorPage();
1716-
await this.dismissWelcomeModal();
1722+
if (flag) {
1723+
await this.dismissWelcomeModal();
1724+
}
17171725
await this.createMinimalExploration(
17181726
'Exploration intro text',
17191727
'End Exploration'
@@ -2058,6 +2066,145 @@ export class ExplorationEditor extends BaseUser {
20582066
await this.waitForNetworkIdle();
20592067
}
20602068

2069+
/**
2070+
* Function to create and save a new untitled exploration containing only the EndExploration interaction.
2071+
*/
2072+
async createAndSaveAMinimalExploration(): Promise<void> {
2073+
await this.navigateToCreatorDashboardPage();
2074+
await this.navigateToExplorationEditorPage();
2075+
await this.createMinimalExploration(
2076+
'Exploration intro text',
2077+
'End Exploration'
2078+
);
2079+
await this.saveExplorationDraft();
2080+
}
2081+
2082+
/**
2083+
* Function to verify the average rating and the number of users who submitted ratings.
2084+
* @param {number} expectedRating - The expected average rating.
2085+
* @param {number} expectedUsers - The expected count of users who submitted ratings.
2086+
*/
2087+
async expectAverageRatingAndUsersToBe(
2088+
expectedRating: number,
2089+
expectedUsers: number
2090+
): Promise<void> {
2091+
await this.page.waitForSelector(avarageRatingSelector, {
2092+
visible: true,
2093+
});
2094+
const avarageRating = await this.page.$eval(
2095+
avarageRatingSelector,
2096+
element => parseFloat((element as HTMLElement).innerText.trim())
2097+
);
2098+
if (avarageRating !== expectedRating) {
2099+
throw new Error(
2100+
`Expected average rating to be ${expectedRating}, but found ${avarageRating}.`
2101+
);
2102+
}
2103+
const totalUsersText = await this.page.$eval(
2104+
usersCountInRatingSelector,
2105+
el => (el as HTMLElement).innerText.trim() || ''
2106+
);
2107+
// Extract number from text (e.g., "by 3 users" → 3).
2108+
const totalUsersMatch = totalUsersText.match(/\d+/);
2109+
const totalUsers = totalUsersMatch ? parseInt(totalUsersMatch[0], 10) : 0;
2110+
if (totalUsers !== expectedUsers) {
2111+
throw new Error(
2112+
`Expected ${expectedUsers} users to have submitted ratings, but found only ${totalUsers} instead.`
2113+
);
2114+
}
2115+
}
2116+
2117+
/**
2118+
* Function to check the expected number of open feedback entries.
2119+
* @param {number} number - The expected count of open feedback entries.
2120+
*/
2121+
async expectOpenFeedbacksToBe(number: number): Promise<void> {
2122+
await this.page.waitForSelector(numberOfOpenFeedbacksSelector, {
2123+
visible: true,
2124+
});
2125+
const numberOfOpenFeedbacks = await this.page.$eval(
2126+
numberOfOpenFeedbacksSelector,
2127+
el => parseInt((el as HTMLElement).innerText.trim(), 10)
2128+
);
2129+
if (numberOfOpenFeedbacks !== number) {
2130+
throw new Error(
2131+
`Expected open feedback count to be ${number}, but found ${numberOfOpenFeedbacks}.`
2132+
);
2133+
}
2134+
}
2135+
2136+
/**
2137+
* Function to check the expected total number of plays."
2138+
* @param {number} number - The expected total play count.
2139+
*/
2140+
async expectTotalPlaysToBe(number: number): Promise<void> {
2141+
await this.page.waitForSelector(totalPlaysSelector, {
2142+
visible: true,
2143+
});
2144+
const numberOfTotalPlays = await this.page.$eval(totalPlaysSelector, el =>
2145+
parseInt((el as HTMLElement).innerText.trim(), 10)
2146+
);
2147+
if (numberOfTotalPlays !== number) {
2148+
throw new Error(
2149+
`Expected total plays count to be ${number}, but found ${numberOfTotalPlays}.`
2150+
);
2151+
}
2152+
}
2153+
2154+
/**
2155+
* Function to check the expected total number of explorations.
2156+
* @param {number} number - The expected count of total explorations.
2157+
*/
2158+
async expectNumberOfExplorationsToBe(number: number): Promise<void> {
2159+
await this.page.waitForSelector(explorationSummaryTileTitleSelector, {
2160+
visible: true,
2161+
});
2162+
const titlesOnPage = await this.page.$$eval(
2163+
explorationSummaryTileTitleSelector,
2164+
elements => elements.map(el => el.textContent?.trim() || '')
2165+
);
2166+
const count = titlesOnPage.length;
2167+
2168+
if (count !== number) {
2169+
throw new Error(
2170+
`Expected ${number} explorations, but found ${count} instead.`
2171+
);
2172+
}
2173+
}
2174+
2175+
/**
2176+
* Function to check the presence and expected number of occurrences of an exploration.
2177+
* @param {string} explorationName - The name of the exploration.
2178+
* @param {number} numberOfOccurrence - The expected occurrence count of the exploration.
2179+
*/
2180+
async expectExplorationNameToAppearNTimes(
2181+
explorationName: string,
2182+
numberOfOccurrence: number = 1
2183+
): Promise<void> {
2184+
await this.page.waitForSelector(explorationSummaryTileTitleSelector, {
2185+
visible: true,
2186+
});
2187+
2188+
// Extract all exploration titles.
2189+
const titlesOnPage = await this.page.$$eval(
2190+
explorationSummaryTileTitleSelector,
2191+
elements => elements.map(el => el.textContent?.trim() || '')
2192+
);
2193+
2194+
// Count occurrences of the target exploration.
2195+
const count = titlesOnPage.filter(
2196+
title => title === explorationName
2197+
).length;
2198+
2199+
if (numberOfOccurrence === 1 && count !== numberOfOccurrence) {
2200+
throw new Error(`Exploration "${explorationName}" not found.`);
2201+
} else if (count !== numberOfOccurrence) {
2202+
throw new Error(
2203+
`Exploration "${explorationName}" found ${count} times, but expected ${numberOfOccurrence} times.`
2204+
);
2205+
}
2206+
}
2207+
20612208
/**
20622209
* Opens an exploration in the editor.
20632210
* @param {string} explorationName - The name of the exploration.

scripts/common.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@
217217
'exploration-editor/load-complete-and-restart-exploration-preview',
218218
'exploration-editor/publish-the-exploration-with-an-interaction',
219219
'exploration-editor/save-draft-publish-and-discard-the-changes',
220+
'exploration-editor/verify-statistics-and-previous-explorations',
220221
'logged-in-user/subscribe-to-creator-and-view-all-'
221222
'explorations-by-that-creator',
222223
'logged-in-user/create-and-delete-account',

0 commit comments

Comments
 (0)