test: e2e make all work again

This commit is contained in:
Johannes Millan 2025-05-10 15:04:17 +02:00
parent d83d883c98
commit 27f31b764f
21 changed files with 485 additions and 311 deletions

View file

@ -9,6 +9,7 @@ const EXPAND_TAG_BTN = `${SIDENAV} .tags .expand-btn`;
const TAGS = `${SIDENAV} section.tags`;
const FINISH_DAY_BTN = '.e2e-finish-day';
const WORK_VIEW = 'work-view';
const TASK_LIST = 'task-list';
export const cssSelectors = {
ADD_TASK_GLOBAL_SEL,
@ -19,4 +20,5 @@ export const cssSelectors = {
TAGS,
FINISH_DAY_BTN,
WORK_VIEW,
TASK_LIST,
};

View file

@ -1,23 +0,0 @@
// const fakeDateTS = new Date('2025-05-09T11:00:00Z').getTime();
module.exports = {
beforeEach: async (browser) => {
// // For newer Nightwatch versions (v2+)
// if (browser.chrome && browser.chrome.sendDevToolsCommand) {
// await browser.chrome.sendDevToolsCommand('Emulation.setVirtualTimePolicy', {
// policy: 'pauseIfNetworkFetchesPending',
// initialVirtualTime: fakeDateTS / 1000,
// });
// }
// // Fallback to older method
// else if (browser.driver) {
// const session = await browser.driver.getDevToolsSession();
// await session.send('Emulation.setVirtualTimePolicy', {
// policy: 'pauseIfNetworkFetchesPending',
// initialVirtualTime: fakeDateTS / 1000,
// });
// } else {
// throw new Error('Unable to simulate other time');
// }
},
};

View file

@ -0,0 +1,17 @@
import { NightwatchCallbackResult } from 'nightwatch';
export const saveMetricsResult = (
result: NightwatchCallbackResult<{ [metricName: string]: number }>,
fileNameSuffix: string,
): Promise<undefined> => {
if (result.status === 0) {
const metrics = result.value;
console.log(metrics);
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require('fs').writeFileSync(
`perf-metrics-${fileNameSuffix}.json`,
JSON.stringify(metrics, null, 2),
);
}
return Promise.reject('Unable to get perf metrics');
};

View file

@ -15,8 +15,7 @@ module.exports = {
test_settings: {
default: {
globals_path: '../out-tsc/e2e/globals.js',
// globals_path: './globals.js', // Point to your globals file
persist_globals: true,
launch_url: 'https://0.0.0.0:4200',
desiredCapabilities: {
browserName: 'chrome',
@ -47,6 +46,36 @@ module.exports = {
waitForConditionPollInterval: 500,
waitForConditionTimeout: 10000,
retryAssertionTimeout: 1000,
beforeEach: async (browser) => {
// const today = new Date();
// today.setHours(17);
// const fakeDateTS = today.getTime();
//
// console.log('XXX');
// browser.execute(() => {
// console.log('AAAAAAa');
// window.e2eTest = true;
// });
//
// // For newer Nightwatch versions (v2+)
// if (browser.chrome && browser.chrome.sendDevToolsCommand) {
// await browser.chrome.sendDevToolsCommand('Emulation.setVirtualTimePolicy', {
// policy: 'pauseIfNetworkFetchesPending',
// initialVirtualTime: fakeDateTS / 1000,
// });
// }
// // Fallback to older method
// else if (browser.driver) {
// const session = await browser.driver.getDevToolsSession();
// await session.send('Emulation.setVirtualTimePolicy', {
// policy: 'pauseIfNetworkFetchesPending',
// initialVirtualTime: fakeDateTS / 1000,
// });
// } else {
// throw new Error('Unable to simulate other time');
// }
},
},
},
},

View file

@ -1,22 +1,20 @@
import { NBrowser } from '../n-browser-interface';
import { cssSelectors, WORK_VIEW_URL } from '../e2e.const';
const { FINISH_DAY_BTN } = cssSelectors;
import { NBrowser } from '../../n-browser-interface';
import { cssSelectors } from '../../e2e.const';
const { TASK_LIST } = cssSelectors;
// const AUTOCOMPLETE = 'mention-list';
// const AUTOCOMPLETE_ITEM = `${AUTOCOMPLETE} .mention-active`;
// const AUTOCOMPLETE_ITEM_TEXT = `${AUTOCOMPLETE_ITEM} .mention-item`;
// const { EXPAND_TAG_BTN, FINISH_DAY_BTN, TAGS } = cssSelectors;
// const TAG = `${TAGS} div.tag`;
const CONFIRM_CREATE_TAG_BTN = `dialog-confirm button[e2e="confirmBtn"]`;
const BASIC_TAG_TITLE = 'task tag-list tag:last-of-type .tag-title';
module.exports = {
'@tags': ['task', 'short-syntax', 'autocomplete'],
before: (browser: NBrowser) => browser.loadAppAndClickAwayWelcomeDialog(),
after: (browser: NBrowser) => browser.end(),
'should create a simple tag': (browser: NBrowser) => {
browser
.loadAppAndClickAwayWelcomeDialog(WORK_VIEW_URL)
.waitForElementVisible(FINISH_DAY_BTN)
.waitForElementVisible(TASK_LIST)
.addTask('some task <3 #basicTag', true)
.waitForElementPresent(CONFIRM_CREATE_TAG_BTN)
@ -24,8 +22,7 @@ module.exports = {
.waitForElementPresent(BASIC_TAG_TITLE)
.assert.elementPresent(BASIC_TAG_TITLE)
.assert.textContains(BASIC_TAG_TITLE, 'basicTag')
.end();
.assert.textContains(BASIC_TAG_TITLE, 'basicTag');
},
// TODO make these work again

View file

@ -1,5 +1,5 @@
import { BASE } from '../e2e.const';
import { NBrowser } from '../n-browser-interface';
import { BASE } from '../../e2e.const';
import { NBrowser } from '../../n-browser-interface';
/* eslint-disable @typescript-eslint/naming-convention */
const URL = `${BASE}/#/tag/TODAY/daily-summary`;
@ -12,12 +12,10 @@ module.exports = {
browser
.loadAppAndClickAwayWelcomeDialog(URL)
.waitForElementVisible('.done-headline')
.assert.textContains('.done-headline', 'Take a moment to celebrate')
.end(),
.assert.textContains('.done-headline', 'Take a moment to celebrate'),
'show any added task in table': (browser: NBrowser) =>
browser
.loadAppAndClickAwayWelcomeDialog(URL)
.addTask('test task hohoho 1h/1h')
.waitForElementVisible(SUMMARY_TABLE_TASK_EL)
.assert.textContains(SUMMARY_TABLE_TASK_EL, 'test task hohoho')

View file

@ -1,8 +1,6 @@
import { BASE } from '../e2e.const';
import { NBrowser } from '../n-browser-interface';
import { NBrowser } from '../../n-browser-interface';
/* eslint-disable @typescript-eslint/naming-convention */
const TARGET_URL = `${BASE}/`;
const PANEL_BTN = '.e2e-toggle-issue-provider-panel';
const ITEMS1 = '.items:nth-of-type(1)';
const ITEMS2 = '.items:nth-of-type(2)';
@ -12,9 +10,11 @@ const CANCEL_BTN = 'mat-dialog-actions button:nth-of-type(1)';
module.exports = {
'@tags': ['issue', 'issue-provider-panel'],
before: (browser: NBrowser) => browser.loadAppAndClickAwayWelcomeDialog(),
after: (browser: NBrowser) => browser.end(),
'should open all dialogs without error': (browser: NBrowser) =>
browser
.loadAppAndClickAwayWelcomeDialog(TARGET_URL)
.waitForElementVisible(PANEL_BTN)
.click(PANEL_BTN)
.waitForElementVisible('issue-provider-setup-overview')
@ -51,7 +51,5 @@ module.exports = {
.waitForElementVisible(CANCEL_BTN)
.click(CANCEL_BTN)
.noError()
.end(),
.noError(),
};

View file

@ -1,64 +1,16 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { NBrowser } from '../n-browser-interface';
import { NightwatchCallbackResult } from 'nightwatch';
import { BASE, cssSelectors } from '../e2e.const';
const { FINISH_DAY_BTN } = cssSelectors;
import { cssSelectors } from '../e2e.const';
import { saveMetricsResult } from '../helper/save-metrics-result';
const TASK = 'task';
const WORK_VIEW_URL = `${BASE}/`;
const saveMetricsResult = (
result: NightwatchCallbackResult<{ [metricName: string]: number }>,
fileNameSuffix: string,
): Promise<undefined> => {
if (result.status === 0) {
const metrics = result.value;
console.log(metrics);
// eslint-disable-next-line @typescript-eslint/no-var-requires
return require('fs').writeFileSync(
`perf-metrics-${fileNameSuffix}.json`,
JSON.stringify(metrics, null, 2),
);
}
return Promise.reject('Unable to get perf metrics');
};
const { TASK_LIST } = cssSelectors;
module.exports = {
'@tags': ['perf', 'performance'],
'perf: initial load': (browser: NBrowser) =>
browser
.enablePerformanceMetrics()
.loadAppAndClickAwayWelcomeDialog(WORK_VIEW_URL)
.waitForElementVisible(FINISH_DAY_BTN)
.getPerformanceMetrics((r) => saveMetricsResult(r, 'initial-load'))
.end(),
'perf: adding tasks': (browser: NBrowser) =>
browser
.enablePerformanceMetrics()
.loadAppAndClickAwayWelcomeDialog(WORK_VIEW_URL)
.waitForElementVisible(FINISH_DAY_BTN)
.addTask('1 test task koko')
.addTask('2 test task koko')
.addTask('3 test task koko')
.addTask('4 test task koko')
.addTask('5 test task koko')
.addTask('6 test task koko')
.addTask('7 test task koko')
.addTask('8 test task koko')
.addTask('9 test task koko')
.addTask('10 test task koko')
.addTask('11 test task koko')
.addTask('12 test task koko')
.addTask('13 test task koko')
.addTask('14 test task koko')
.addTask('15 test task koko')
.addTask('16 test task koko')
.addTask('17 test task koko')
.addTask('18 test task koko')
.addTask('19 test task koko')
.addTask('20 test task koko')
.waitForElementVisible(TASK)
.getPerformanceMetrics((r) => saveMetricsResult(r, 'create-tasks'))
.end(),
.loadAppAndClickAwayWelcomeDialog()
.waitForElementVisible(TASK_LIST)
.getPerformanceMetrics((r) => saveMetricsResult(r, 'initial-load')),
};

39
e2e/src/perf2.e2e.ts Normal file
View file

@ -0,0 +1,39 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { NBrowser } from '../n-browser-interface';
import { cssSelectors } from '../e2e.const';
import { saveMetricsResult } from '../helper/save-metrics-result';
const { TASK_LIST } = cssSelectors;
const TASK = 'task';
module.exports = {
'@tags': ['perf', 'performance'],
'perf: adding tasks': (browser: NBrowser) =>
browser
.enablePerformanceMetrics()
.loadAppAndClickAwayWelcomeDialog()
.waitForElementVisible(TASK_LIST)
.addTask('1 test task koko')
.addTask('2 test task koko')
.addTask('3 test task koko')
.addTask('4 test task koko')
.addTask('5 test task koko')
.addTask('6 test task koko')
.addTask('7 test task koko')
.addTask('8 test task koko')
.addTask('9 test task koko')
.addTask('10 test task koko')
.addTask('11 test task koko')
.addTask('12 test task koko')
.addTask('13 test task koko')
.addTask('14 test task koko')
.addTask('15 test task koko')
.addTask('16 test task koko')
.addTask('17 test task koko')
.addTask('18 test task koko')
.addTask('19 test task koko')
.addTask('20 test task koko')
.waitForElementVisible(TASK)
.getPerformanceMetrics((r) => saveMetricsResult(r, 'create-tasks')),
};

View file

@ -1,4 +1,4 @@
import { NBrowser } from '../n-browser-interface';
import { NBrowser } from '../../n-browser-interface';
/* eslint-disable @typescript-eslint/naming-convention */
const NOTES_WRAPPER = 'notes';
@ -9,6 +9,9 @@ const TOGGLE_NOTES_BTN = '.e2e-toggle-notes-btn';
module.exports = {
'@tags': ['note'],
before: (browser: NBrowser) => browser.loadAppAndClickAwayWelcomeDialog(),
after: (browser: NBrowser) => browser.end(),
'create a note': (browser: NBrowser) =>
browser
.createAndGoToDefaultProject()
@ -16,14 +19,10 @@ module.exports = {
.moveToElement(NOTES_WRAPPER, 10, 50)
.waitForElementVisible(FIRST_NOTE)
.assert.textContains(FIRST_NOTE, 'Some new Note')
.end(),
.assert.textContains(FIRST_NOTE, 'Some new Note'),
'new note should be still available after reload': (browser: NBrowser) =>
browser
.createAndGoToDefaultProject()
.addNote('Some new Note')
// wait for save
.pause(200)
.execute('window.location.reload()')
@ -33,6 +32,5 @@ module.exports = {
.moveToElement(NOTES_WRAPPER, 10, 50)
.waitForElementVisible(FIRST_NOTE)
.assert.elementPresent(FIRST_NOTE)
.assert.textContains(FIRST_NOTE, 'Some new Note')
.end(),
.assert.textContains(FIRST_NOTE, 'Some new Note'),
};

View file

@ -1,5 +1,5 @@
import { NBrowser } from '../n-browser-interface';
import { cssSelectors } from '../e2e.const';
import { NBrowser } from '../../n-browser-interface';
import { cssSelectors } from '../../e2e.const';
const { WORK_VIEW } = cssSelectors;
@ -24,38 +24,44 @@ const PROJECT_SETTINGS_BTN = `${WORK_CTX_MENU} button:nth-of-type(4)`;
const SECOND_PROJECT = `${PROJECT}:nth-of-type(2)`;
const SECOND_PROJECT_BTN = `${SECOND_PROJECT} button:first-of-type`;
// const BACKLOG = `.backlog`;
// const SPLIT = `split`;
const MOVE_TO_ARCHIVE_BTN = '.e2e-move-done-to-archive';
const GLOBAL_ERROR_ALERT = '.global-error-alert';
module.exports = {
'@tags': ['project'],
'navigate to project settings': (browser: NBrowser) =>
before: (browser: NBrowser) =>
browser.loadAppAndClickAwayWelcomeDialog().createAndGoToDefaultProject(),
after: (browser: NBrowser) => browser.end(),
'move done tasks to archive without error': (browser: NBrowser) =>
browser
.loadAppAndClickAwayWelcomeDialog()
.createAndGoToDefaultProject()
.waitForElementVisible(DEFAULT_PROJECT)
.waitForElementVisible(DEFAULT_PROJECT_BTN)
.moveToElement(DEFAULT_PROJECT_BTN, 20, 20)
.waitForElementVisible(DEFAULT_PROJECT_ADV_BTN)
.click(DEFAULT_PROJECT_ADV_BTN)
.waitForElementVisible(WORK_CTX_MENU)
.waitForElementVisible(PROJECT_SETTINGS_BTN)
// navigate to
.click(PROJECT_SETTINGS_BTN)
.waitForElementVisible('.component-wrapper .mat-h1')
.assert.textContains('.component-wrapper .mat-h1', 'Project Specific Settings')
.end(),
.click(WORK_VIEW)
.addTask('Test task 1')
.addTask('Test task 2')
.moveToElement('task', 12, 12)
.waitForElementVisible('.task-done-btn')
.click('.task-done-btn')
// workaround for weird collapsible state during headless ???
.execute(
(moveToArchiveBtnSelector) => {
if (!document.querySelector(moveToArchiveBtnSelector)) {
const header = document.querySelector('.collapsible-header');
if (header) (header as HTMLElement).click();
}
return true;
},
[MOVE_TO_ARCHIVE_BTN],
)
.pause(100) // Give time for the header to expand if needed
.waitForElementVisible(MOVE_TO_ARCHIVE_BTN)
.click(MOVE_TO_ARCHIVE_BTN)
.pause(500)
.assert.elementPresent('task:nth-child(1)')
.assert.not.elementPresent(GLOBAL_ERROR_ALERT),
'create second project': (browser: NBrowser) =>
browser
.loadAppAndClickAwayWelcomeDialog()
.createAndGoToDefaultProject()
.moveToElement(PROJECT_ACCORDION, 20, 15)
.waitForElementVisible(CREATE_PROJECT_BTN)
@ -74,38 +80,22 @@ module.exports = {
// .waitForElementVisible(BACKLOG)
// .waitForElementVisible(SPLIT)
.assert.textContains(WORK_CTX_TITLE, 'Cool Test Project')
.end(),
.assert.textContains(WORK_CTX_TITLE, 'Cool Test Project'),
'move done tasks to archive without error': (browser: NBrowser) =>
'navigate to project settings': (browser: NBrowser) =>
browser
// Go to project page
.createAndGoToDefaultProject()
// HERE TO: avoid Error while running .clickElement() protocol action:
// stale element reference: stale element not found in the current frame
.pause(200)
.click(WORK_VIEW)
.addTask('Test task 1')
.addTask('Test task 2')
.moveToElement('task', 50, 20)
.pause(50)
.click('.task-done-btn')
// workaround for weird collapsible state during headless ???
.execute(
(moveToArchiveBtnSelector) => {
if (!document.querySelector(moveToArchiveBtnSelector)) {
const header = document.querySelector('.collapsible-header');
if (header) (header as HTMLElement).click();
}
return true;
},
[MOVE_TO_ARCHIVE_BTN],
)
.pause(100) // Give time for the header to expand if needed
.waitForElementVisible(MOVE_TO_ARCHIVE_BTN)
.click(MOVE_TO_ARCHIVE_BTN)
.pause(500)
.assert.elementPresent('task:nth-child(1)')
.assert.not.elementPresent(GLOBAL_ERROR_ALERT)
.end(),
.waitForElementVisible(DEFAULT_PROJECT)
.waitForElementVisible(DEFAULT_PROJECT_BTN)
.moveToElement(DEFAULT_PROJECT_BTN, 20, 20)
.waitForElementVisible(DEFAULT_PROJECT_ADV_BTN)
.click(DEFAULT_PROJECT_ADV_BTN)
.waitForElementVisible(WORK_CTX_MENU)
.waitForElementVisible(PROJECT_SETTINGS_BTN)
// navigate to
.click(PROJECT_SETTINGS_BTN)
.waitForElementVisible('.component-wrapper .mat-h1')
.assert.textContains('.component-wrapper .mat-h1', 'Project Specific Settings')
.click('body'),
};

View file

@ -1,91 +0,0 @@
import { BASE } from '../e2e.const';
import { NBrowser } from '../n-browser-interface';
/* eslint-disable @typescript-eslint/naming-convention */
const WORK_VIEW_URL = `${BASE}/`;
const DIALOG = 'dialog-view-task-reminder';
const DIALOG_TASKS_WRAPPER = `${DIALOG} .tasks`;
const DIALOG_TASK = `${DIALOG} .task`;
const DIALOG_TASK1 = `${DIALOG_TASK}:first-of-type`;
const DIALOG_TASK2 = `${DIALOG_TASK}:nth-of-type(2)`;
const DIALOG_TASK3 = `${DIALOG_TASK}:nth-of-type(3)`;
const TO_TODAY_SUF = ' .actions button:last-of-type';
const D_ACTIONS = `${DIALOG} mat-dialog-actions`;
const D_PLAY = `${D_ACTIONS} button:last-of-type`;
const TODAY_TASKS = 'task-list task';
const TODAY_TASK_1 = `${TODAY_TASKS}:first-of-type`;
const SCHEDULE_MAX_WAIT_TIME = 180000;
module.exports = {
'@tags': ['task', 'reminder', 'schedule'],
'should display a modal with a scheduled task if due': (browser: NBrowser) =>
browser
.loadAppAndClickAwayWelcomeDialog(WORK_VIEW_URL)
.addTaskWithReminder({ title: '0 A task', scheduleTime: Date.now() })
.waitForElementVisible(DIALOG, SCHEDULE_MAX_WAIT_TIME)
.assert.elementPresent(DIALOG)
.waitForElementVisible(DIALOG_TASK1)
.assert.elementPresent(DIALOG_TASK1)
.assert.textContains(DIALOG_TASK1, '0 A task')
.end(),
'should display a modal with 2 scheduled task if due': (browser: NBrowser) => {
return (
browser
.loadAppAndClickAwayWelcomeDialog(WORK_VIEW_URL)
// NOTE: tasks are sorted by due time
.addTaskWithReminder({ title: '0 B task' })
.addTaskWithReminder({ title: '1 B task', scheduleTime: Date.now() })
.waitForElementVisible(DIALOG, SCHEDULE_MAX_WAIT_TIME)
.assert.elementPresent(DIALOG)
.waitForElementVisible(DIALOG_TASK1, SCHEDULE_MAX_WAIT_TIME)
.waitForElementVisible(DIALOG_TASK2, SCHEDULE_MAX_WAIT_TIME)
.assert.textContains(DIALOG_TASKS_WRAPPER, '0 B task')
.assert.textContains(DIALOG_TASKS_WRAPPER, '1 B task')
.end()
);
},
'should start single task': (browser: NBrowser) =>
browser
.loadAppAndClickAwayWelcomeDialog(WORK_VIEW_URL)
.addTaskWithReminder({ title: '0 C task', scheduleTime: Date.now() })
.waitForElementVisible(DIALOG, SCHEDULE_MAX_WAIT_TIME)
.waitForElementVisible(DIALOG_TASK1)
.click(D_PLAY)
.pause(100)
.assert.hasClass(TODAY_TASK_1, 'isCurrent')
.end(),
'should manually empty list via add to today': (browser: NBrowser) => {
const start = Date.now() + 100000;
return (
browser
.loadAppAndClickAwayWelcomeDialog(WORK_VIEW_URL)
// NOTE: tasks are sorted by due time
.addTaskWithReminder({ title: '0 D task xyz', scheduleTime: start })
.addTaskWithReminder({ title: '1 D task xyz', scheduleTime: start })
.addTaskWithReminder({ title: '2 D task xyz', scheduleTime: Date.now() })
.waitForElementVisible(DIALOG, SCHEDULE_MAX_WAIT_TIME + 120000)
// wait for all tasks to be present
.waitForElementVisible(DIALOG_TASK1, SCHEDULE_MAX_WAIT_TIME + 120000)
.waitForElementVisible(DIALOG_TASK2, SCHEDULE_MAX_WAIT_TIME + 120000)
.waitForElementVisible(DIALOG_TASK3, SCHEDULE_MAX_WAIT_TIME + 120000)
.pause(100)
.assert.textContains(DIALOG_TASKS_WRAPPER, '0 D task xyz')
.assert.textContains(DIALOG_TASKS_WRAPPER, '1 D task xyz')
.assert.textContains(DIALOG_TASKS_WRAPPER, '2 D task xyz')
.click(DIALOG_TASK1 + TO_TODAY_SUF)
.click(DIALOG_TASK2 + TO_TODAY_SUF)
.pause(50)
.assert.textContains(DIALOG_TASK1, 'D task xyz')
.end()
);
},
};

View file

@ -1,11 +1,9 @@
import { BASE, cssSelectors } from '../e2e.const';
import { NBrowser } from '../n-browser-interface';
import { cssSelectors } from '../../e2e.const';
import { NBrowser } from '../../n-browser-interface';
const { FINISH_DAY_BTN } = cssSelectors;
const { TASK_LIST } = cssSelectors;
/* eslint-disable @typescript-eslint/naming-convention */
const WORK_VIEW_URL = `${BASE}/`;
const TASK = 'task';
const TASK_2 = `${TASK}:nth-of-type(1)`;
const TASK_SCHEDULE_BTN = '.ico-btn.schedule-btn';
@ -23,11 +21,12 @@ const SCHEDULE_PAGE_TASK_2_TITLE_EL = `${SCHEDULE_PAGE_TASK_2} .title`;
module.exports = {
'@tags': ['task', 'reminder'],
before: (browser: NBrowser) => browser.loadAppAndClickAwayWelcomeDialog(),
after: (browser: NBrowser) => browser.end(),
'should add a scheduled tasks': (browser: NBrowser) =>
browser
.loadAppAndClickAwayWelcomeDialog(WORK_VIEW_URL)
.waitForElementPresent(FINISH_DAY_BTN)
.waitForElementPresent(TASK_LIST)
.addTaskWithReminder({ title: '0 test task koko', scheduleTime: Date.now() })
.waitForElementVisible(TASK)
.waitForElementVisible(TASK_SCHEDULE_BTN)
@ -38,15 +37,18 @@ module.exports = {
.waitForElementVisible(SCHEDULE_PAGE_CMP)
.waitForElementVisible(SCHEDULE_PAGE_TASK_1)
.waitForElementVisible(SCHEDULE_PAGE_TASK_1_TITLE_EL)
.assert.textContains(SCHEDULE_PAGE_TASK_1_TITLE_EL, '0 test task koko')
.end(),
.assert.textContains(SCHEDULE_PAGE_TASK_1_TITLE_EL, '0 test task koko'),
'should add multiple scheduled tasks': (browser: NBrowser) =>
browser
.loadAppAndClickAwayWelcomeDialog(WORK_VIEW_URL)
.waitForElementPresent(FINISH_DAY_BTN)
.addTaskWithReminder({ title: '0 test task koko', taskSel: TASK })
.addTaskWithReminder({ title: '2 hihihi', taskSel: TASK_2 })
.click('.current-work-context-title')
.waitForElementPresent(TASK_LIST)
.pause(1000)
.addTaskWithReminder({
title: '2 hihihi',
taskSel: TASK_2,
scheduleTime: Date.now(),
})
.waitForElementVisible(TASK)
.waitForElementVisible(TASK_SCHEDULE_BTN)
.assert.elementPresent(TASK_SCHEDULE_BTN)
@ -58,6 +60,5 @@ module.exports = {
.waitForElementVisible(SCHEDULE_PAGE_TASK_1)
.waitForElementVisible(SCHEDULE_PAGE_TASK_1_TITLE_EL)
.assert.textContains(SCHEDULE_PAGE_TASK_1_TITLE_EL, '0 test task koko')
.assert.textContains(SCHEDULE_PAGE_TASK_2_TITLE_EL, '2 hihihi')
.end(),
.assert.textContains(SCHEDULE_PAGE_TASK_2_TITLE_EL, '2 hihihi'),
};

View file

@ -0,0 +1,23 @@
import { NBrowser } from '../../n-browser-interface';
/* eslint-disable @typescript-eslint/naming-convention */
const DIALOG = 'dialog-view-task-reminder';
const DIALOG_TASK = `${DIALOG} .task`;
const DIALOG_TASK1 = `${DIALOG_TASK}:first-of-type`;
const SCHEDULE_MAX_WAIT_TIME = 180000;
module.exports = {
'@tags': ['task', 'reminder', 'schedule'],
before: (browser: NBrowser) => browser.loadAppAndClickAwayWelcomeDialog(),
after: (browser: NBrowser) => browser.end(),
'should display a modal with a scheduled task if due': (browser: NBrowser) =>
browser
.addTaskWithReminder({ title: '0 A task', scheduleTime: Date.now() })
.waitForElementVisible(DIALOG, SCHEDULE_MAX_WAIT_TIME)
.assert.elementPresent(DIALOG)
.waitForElementVisible(DIALOG_TASK1)
.assert.elementPresent(DIALOG_TASK1)
.assert.textContains(DIALOG_TASK1, '0 A task'),
};

View file

@ -0,0 +1,32 @@
import { NBrowser } from '../../n-browser-interface';
/* eslint-disable @typescript-eslint/naming-convention */
const DIALOG = 'dialog-view-task-reminder';
const DIALOG_TASKS_WRAPPER = `${DIALOG} .tasks`;
const DIALOG_TASK = `${DIALOG} .task`;
const DIALOG_TASK1 = `${DIALOG_TASK}:first-of-type`;
const DIALOG_TASK2 = `${DIALOG_TASK}:nth-of-type(2)`;
const SCHEDULE_MAX_WAIT_TIME = 180000;
module.exports = {
'@tags': ['task', 'reminder', 'schedule'],
before: (browser: NBrowser) => browser.loadAppAndClickAwayWelcomeDialog(),
after: (browser: NBrowser) => browser.end(),
'should display a modal with 2 scheduled task if due': (browser: NBrowser) => {
return (
browser
// NOTE: tasks are sorted by due time
.addTaskWithReminder({ title: '0 B task' })
.addTaskWithReminder({ title: '1 B task', scheduleTime: Date.now() })
.waitForElementVisible(DIALOG, SCHEDULE_MAX_WAIT_TIME)
.assert.elementPresent(DIALOG)
.waitForElementVisible(DIALOG_TASK1, SCHEDULE_MAX_WAIT_TIME)
.waitForElementVisible(DIALOG_TASK2, SCHEDULE_MAX_WAIT_TIME)
.assert.textContains(DIALOG_TASKS_WRAPPER, '0 B task')
.assert.textContains(DIALOG_TASKS_WRAPPER, '1 B task')
);
},
};

View file

@ -0,0 +1,30 @@
import { NBrowser } from '../../n-browser-interface';
/* eslint-disable @typescript-eslint/naming-convention */
const DIALOG = 'dialog-view-task-reminder';
const DIALOG_TASK = `${DIALOG} .task`;
const DIALOG_TASK1 = `${DIALOG_TASK}:first-of-type`;
const D_ACTIONS = `${DIALOG} mat-dialog-actions`;
const D_PLAY = `${D_ACTIONS} button:last-of-type`;
const TODAY_TASKS = 'task-list task';
const TODAY_TASK_1 = `${TODAY_TASKS}:first-of-type`;
const SCHEDULE_MAX_WAIT_TIME = 180000;
module.exports = {
'@tags': ['task', 'reminder', 'schedule'],
before: (browser: NBrowser) => browser.loadAppAndClickAwayWelcomeDialog(),
after: (browser: NBrowser) => browser.end(),
'should start single task': (browser: NBrowser) =>
browser
.addTaskWithReminder({ title: '0 C task', scheduleTime: Date.now() })
.waitForElementVisible(DIALOG, SCHEDULE_MAX_WAIT_TIME)
.waitForElementVisible(DIALOG_TASK1)
.click(D_PLAY)
.pause(100)
.assert.hasClass(TODAY_TASK_1, 'isCurrent'),
};

View file

@ -0,0 +1,43 @@
import { NBrowser } from '../../n-browser-interface';
/* eslint-disable @typescript-eslint/naming-convention */
const DIALOG = 'dialog-view-task-reminder';
const DIALOG_TASKS_WRAPPER = `${DIALOG} .tasks`;
const DIALOG_TASK = `${DIALOG} .task`;
const DIALOG_TASK1 = `${DIALOG_TASK}:first-of-type`;
const DIALOG_TASK2 = `${DIALOG_TASK}:nth-of-type(2)`;
const DIALOG_TASK3 = `${DIALOG_TASK}:nth-of-type(3)`;
const TO_TODAY_SUF = ' .actions button:last-of-type';
const SCHEDULE_MAX_WAIT_TIME = 180000;
module.exports = {
'@tags': ['task', 'reminder', 'schedule'],
before: (browser: NBrowser) => browser.loadAppAndClickAwayWelcomeDialog(),
after: (browser: NBrowser) => browser.end(),
'should manually empty list via add to today': (browser: NBrowser) => {
const start = Date.now() + 100000;
return (
browser
// NOTE: tasks are sorted by due time
.addTaskWithReminder({ title: '0 D task xyz', scheduleTime: start })
.addTaskWithReminder({ title: '1 D task xyz', scheduleTime: start })
.addTaskWithReminder({ title: '2 D task xyz', scheduleTime: Date.now() })
.waitForElementVisible(DIALOG, SCHEDULE_MAX_WAIT_TIME + 120000)
// wait for all tasks to be present
.waitForElementVisible(DIALOG_TASK1, SCHEDULE_MAX_WAIT_TIME + 120000)
.waitForElementVisible(DIALOG_TASK2, SCHEDULE_MAX_WAIT_TIME + 120000)
.waitForElementVisible(DIALOG_TASK3, SCHEDULE_MAX_WAIT_TIME + 120000)
.pause(100)
.assert.textContains(DIALOG_TASKS_WRAPPER, '0 D task xyz')
.assert.textContains(DIALOG_TASKS_WRAPPER, '1 D task xyz')
.assert.textContains(DIALOG_TASKS_WRAPPER, '2 D task xyz')
.click(DIALOG_TASK1 + TO_TODAY_SUF)
.click(DIALOG_TASK2 + TO_TODAY_SUF)
.pause(50)
.assert.textContains(DIALOG_TASK1, 'D task xyz')
);
},
};

View file

@ -1,33 +1,30 @@
import { NBrowser } from '../n-browser-interface';
import { BASE, cssSelectors, WORK_VIEW_URL } from '../e2e.const';
import { NBrowser } from '../../n-browser-interface';
import { cssSelectors } from '../../e2e.const';
const { FINISH_DAY_BTN, ADD_TASK_GLOBAL_SEL } = cssSelectors;
const { TASK_LIST, ADD_TASK_GLOBAL_SEL } = cssSelectors;
const CONFIRM_CREATE_TAG_BTN = `dialog-confirm button[e2e="confirmBtn"]`;
const BASIC_TAG_TITLE = 'task tag-list tag:last-of-type .tag-title';
const TASK_TAG_SELECTOR = 'task tag-list tag';
const FIRST_TASK_TAG_SELECTOR = 'task:first-of-type tag-list tag';
const TASK = 'task';
const TASK_TAGS = 'task tag';
const WORK_VIEW_URL_FULL = `${BASE}/`;
module.exports = {
'@tags': ['task', 'short-syntax', 'autocomplete', 'work-view'],
before: (browser: NBrowser) => browser.loadAppAndClickAwayWelcomeDialog(),
after: (browser: NBrowser) => browser.end(),
'should add task with project via short syntax': (browser: NBrowser) =>
browser
.loadAppAndClickAwayWelcomeDialog(WORK_VIEW_URL_FULL)
.waitForElementVisible(FINISH_DAY_BTN)
.waitForElementVisible(TASK_LIST)
.addTask('0 test task koko +i')
.waitForElementVisible(TASK)
.assert.visible(TASK)
.assert.containsText(TASK_TAGS, 'Inbox')
.end(),
.assert.containsText(TASK_TAGS, 'Inbox'),
'should add a task with repeated tags but only append one instance': (
browser: NBrowser,
) => {
browser
.loadAppAndClickAwayWelcomeDialog(WORK_VIEW_URL)
.waitForElementVisible(FINISH_DAY_BTN)
.setValue('body', 'A')
.waitForElementVisible(ADD_TASK_GLOBAL_SEL)
.setValue(ADD_TASK_GLOBAL_SEL, `Test creating new tag #duplicateTag #duplicateTag`)
@ -41,7 +38,7 @@ module.exports = {
.assert.textContains(BASIC_TAG_TITLE, 'duplicateTag')
// Verify that only one tag is appended
.elements(`css selector`, TASK_TAG_SELECTOR, (result) => {
.elements(`css selector`, FIRST_TASK_TAG_SELECTOR, (result) => {
if (Array.isArray(result.value)) {
console.log(result);
@ -56,7 +53,6 @@ module.exports = {
console.error('Unexpected result format:', result.value);
browser.assert.fail('Failed to retrieve elements correctly');
}
})
.end();
});
},
};

View file

@ -0,0 +1,120 @@
import { NBrowser } from '../../n-browser-interface';
import { cssSelectors } from '../../e2e.const';
const { TASK_LIST } = cssSelectors;
/* eslint-disable @typescript-eslint/naming-convention */
const TASK = 'task';
const TASK_TEXTAREA = 'task textarea';
// const ADD_TASK_GLOBAL = 'add-task-bar.global input';
// const ADD_TASK_BTN = '.action-nav > button:first-child';
module.exports = {
'@tags': ['work-view', 'task', 'task-standard', 'AAA'],
before: (browser: NBrowser) => browser.loadAppAndClickAwayWelcomeDialog(),
after: (browser: NBrowser) => browser.end(),
'should add task via key combo': (browser: NBrowser) =>
browser
.waitForElementVisible(TASK_LIST)
.addTask('0 test task koko')
.waitForElementVisible(TASK)
.assert.visible(TASK)
.assert.valueContains(TASK_TEXTAREA, '0 test task koko'),
// 'should add multiple tasks from header button': (browser: NBrowser) =>
// browser
// .click(ADD_TASK_BTN)
// .waitForElementVisible(ADD_TASK_GLOBAL)
//
// .sendKeysToActiveEl([
// '4 test task hohoho',
// browser.Keys.ENTER,
// '5 some other task xoxo',
// browser.Keys.ENTER,
// ])
//
// .waitForElementVisible(TASK)
// .assert.visible(TASK)
// // NOTE: global adds to top rather than bottom
// .assert.valueContains(TASK + ':nth-child(1) textarea', '5 some other task xoxo')
// .assert.valueContains(TASK + ':nth-child(2) textarea', '4 test task hohoho'),
// 'should focus previous subtask when marking last subtask done': (browser: NBrowser) =>
// browser
// .addTask('task1')
// .addTask('task2')
// .sendKeysToActiveEl('a')
// .sendKeysToActiveEl(['task3', browser.Keys.ENTER])
// .setValue('task-list task-list task:nth-child(1)', 'a')
// .sendKeysToActiveEl(['task4', browser.Keys.ENTER])
// .waitForElementVisible('.sub-tasks task-list task:nth-child(2)')
// // .moveToElement('.sub-tasks .task-list-inner task:nth-child(2)', 30, 30)
// .moveToElement('.sub-tasks task:nth-child(1)', 30, 30)
// .click('.task-done-btn')
// .execute(
// () => document.activeElement,
// (result) => browser.assert.textContains(result.value as any, 'task3'),
// ),
// 'should still show created task after reload': (browser: NBrowser) =>
// browser
// .addTask('0 test task lolo')
// .waitForElementVisible(TASK)
// .execute('window.location.reload()')
//
// .waitForElementVisible(TASK)
// .assert.visible(TASK)
// .assert.valueContains(TASK_TEXTAREA, '0 test task lolo'),
// .assert.textContains(':focus', 'task3')
// 'should add a task from initial bar': (browser: NBrowser) =>
// browser
// .click(ADD_MORE_BTN)
// .waitForElementVisible(ADD_TASK_INITIAL)
//
// .setValue(ADD_TASK_INITIAL, '1 test task hihi')
// .setValue(ADD_TASK_INITIAL, browser.Keys.ENTER)
//
// .waitForElementVisible(TASK)
// .assert.visible(TASK)
// .assert.valueContains(TASK_TEXTAREA, '1 test task hihi'),
// 'should add 2 tasks from initial bar': (browser: NBrowser) =>
// browser
// .click(ADD_MORE_BTN)
// .waitForElementVisible(ADD_TASK_INITIAL)
//
// .setValue(ADD_TASK_INITIAL, '2 test task hihi')
// .setValue(ADD_TASK_INITIAL, browser.Keys.ENTER)
// .setValue(ADD_TASK_INITIAL, '3 some other task')
// .setValue(ADD_TASK_INITIAL, browser.Keys.ENTER)
//
// .waitForElementVisible(TASK)
// .assert.visible(TASK)
// .assert.valueContains(TASK + ':nth-child(1) textarea', '3 some other task')
// .assert.valueContains(TASK + ':nth-child(2) textarea', '2 test task hihi'),
// 'should add 3 tasks from initial bar and remove 2 of them via the default keyboard shortcut':
// (browser: NBrowser) =>
// browser
// .click(ADD_MORE_BTN)
// .waitForElementVisible(ADD_TASK_INITIAL)
//
// .addTask('3 hihi some other task')
// .addTask('2 some other task')
// .addTask('1 test task hihi')
//
// .waitForElementVisible(TASK)
//
// // focus
// .sendKeysToActiveEl(browser.Keys.TAB)
// .sendKeysToActiveEl(browser.Keys.TAB)
//
// .sendKeysToActiveEl(browser.Keys.BACK_SPACE)
//
// .sendKeysToActiveEl(browser.Keys.BACK_SPACE)
// .waitForElementNotPresent(TASK + ':nth-child(2)')
//
// .assert.valueContains(TASK_TEXTAREA, '1 test task hihi'),
};

69
package-lock.json generated
View file

@ -118,7 +118,7 @@
"new-github-issue-url": "^1.1.0",
"ng2-charts": "^8.0.0",
"ngx-markdown": "^19.0.0",
"nightwatch": "^3.6.3",
"nightwatch": "^3.12.1",
"p-throttle": "^7.0.0",
"prettier": "^3.5.1",
"pretty-quick": "^4.1.1",
@ -2785,8 +2785,9 @@
},
"node_modules/@bazel/runfiles": {
"version": "6.3.1",
"dev": true,
"license": "Apache-2.0"
"resolved": "https://registry.npmjs.org/@bazel/runfiles/-/runfiles-6.3.1.tgz",
"integrity": "sha512-1uLNT5NZsUVIGS4syuHwTzZ8HycMPyr6POA3FCE4GbMtc4rhoJk8aZKtNIRthJYfL+iioppi+rTfH3olMPr9nA==",
"dev": true
},
"node_modules/@braintree/sanitize-url": {
"version": "7.1.0",
@ -15370,8 +15371,9 @@
},
"node_modules/immediate": {
"version": "3.0.6",
"dev": true,
"license": "MIT"
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"dev": true
},
"node_modules/immutable": {
"version": "5.1.1",
@ -16594,8 +16596,9 @@
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"dev": true,
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
@ -16605,13 +16608,15 @@
},
"node_modules/jszip/node_modules/isarray": {
"version": "1.0.0",
"dev": true,
"license": "MIT"
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true
},
"node_modules/jszip/node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true,
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@ -16624,13 +16629,15 @@
},
"node_modules/jszip/node_modules/safe-buffer": {
"version": "5.1.2",
"dev": true,
"license": "MIT"
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true
},
"node_modules/jszip/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
@ -17192,8 +17199,9 @@
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
@ -18694,9 +18702,10 @@
}
},
"node_modules/nightwatch": {
"version": "3.9.0",
"version": "3.12.1",
"resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-3.12.1.tgz",
"integrity": "sha512-+tTSjw7w2xY3TRdq5qMkgSztEk6ofMw1l/+Tc4GVRTyUi2/43qV4m2EzOi8Q/3ERKZrIR1Wqqyyh3pw6PJSKMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nightwatch/chai": "5.0.3",
"@nightwatch/html-reporter-template": "^0.3.0",
@ -18726,7 +18735,7 @@
"open": "8.4.2",
"ora": "5.4.1",
"piscina": "^4.3.1",
"selenium-webdriver": "4.26.0",
"selenium-webdriver": "4.27.0",
"semver": "7.5.4",
"stacktrace-parser": "0.1.10",
"strip-ansi": "6.0.1",
@ -20136,8 +20145,9 @@
},
"node_modules/pako": {
"version": "1.0.11",
"dev": true,
"license": "(MIT AND Zlib)"
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true
},
"node_modules/parent-module": {
"version": "1.0.1",
@ -22134,9 +22144,20 @@
"dev": true
},
"node_modules/selenium-webdriver": {
"version": "4.26.0",
"version": "4.27.0",
"resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.27.0.tgz",
"integrity": "sha512-LkTJrNz5socxpPnWPODQ2bQ65eYx9JK+DQMYNihpTjMCqHwgWGYQnQTCAAche2W3ZP87alA+1zYPvgS8tHNzMQ==",
"dev": true,
"license": "Apache-2.0",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/SeleniumHQ"
},
{
"type": "opencollective",
"url": "https://opencollective.com/selenium"
}
],
"dependencies": {
"@bazel/runfiles": "^6.3.1",
"jszip": "^3.10.1",
@ -22149,8 +22170,9 @@
},
"node_modules/selenium-webdriver/node_modules/tmp": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
"integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.14"
}
@ -22415,8 +22437,9 @@
},
"node_modules/setimmediate": {
"version": "1.0.5",
"dev": true,
"license": "MIT"
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"dev": true
},
"node_modules/setprototypeof": {
"version": "1.2.0",

View file

@ -205,7 +205,7 @@
"new-github-issue-url": "^1.1.0",
"ng2-charts": "^8.0.0",
"ngx-markdown": "^19.0.0",
"nightwatch": "^3.6.3",
"nightwatch": "^3.12.1",
"p-throttle": "^7.0.0",
"prettier": "^3.5.1",
"pretty-quick": "^4.1.1",