Remove testcafe roles and run in parallel x4 (#334)
* more attempts to fix test flakiness * remove testcafe roles entirely * really remove testcafe roles * run testcafe in parallel x2 * run testcafe in parallel x4 * fix online/offline forcing in tests * fix pin test
This commit is contained in:
		
							parent
							
								
									520bf7456b
								
							
						
					
					
						commit
						efdb0bc534
					
				
					 42 changed files with 300 additions and 191 deletions
				
			
		
							
								
								
									
										15
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										15
									
								
								package.json
									
										
									
									
									
								
							|  | @ -8,19 +8,22 @@ | |||
|     "dev": "run-s build-svg build-inline-script serve-dev", | ||||
|     "serve-dev": "run-p --race build-sass-watch serve", | ||||
|     "serve": "node server.js", | ||||
|     "build": "cross-env NODE_ENV=production run-s globalize-css build-sass build-svg build-inline-script sapper-build deglobalize-css", | ||||
|     "sapper-build": "cross-env NODE_ENV=production sapper build", | ||||
|     "start": "cross-env NODE_ENV=production node server.js", | ||||
|     "build": "cross-env NODE_ENV=production npm run build-steps", | ||||
|     "build-steps": "run-s globalize-css build-sass build-svg build-inline-script sapper-build deglobalize-css", | ||||
|     "sapper-build": "sapper build", | ||||
|     "start": "cross-env NODE_ENV=production npm run serve", | ||||
|     "build-and-start": "run-s build start", | ||||
|     "build-svg": "node ./bin/build-svg.js", | ||||
|     "build-inline-script": "node ./bin/build-inline-script.js", | ||||
|     "build-sass": "node ./bin/build-sass.js", | ||||
|     "build-sass-watch": "node ./bin/build-sass.js --watch", | ||||
|     "run-mastodon": "node -r esm ./bin/run-mastodon.js", | ||||
|     "run-testcafe": "cross-env-shell testcafe --hostname localhost --skip-js-errors $BROWSER tests/spec", | ||||
|     "testcafe": "run-s testcafe-p testcafe-s", | ||||
|     "testcafe-p": "cross-env-shell testcafe --hostname localhost --skip-js-errors -c 4 $BROWSER tests/spec/0*", | ||||
|     "testcafe-s": "cross-env-shell testcafe --hostname localhost --skip-js-errors $BROWSER tests/spec/1*", | ||||
|     "test": "cross-env BROWSER=chrome:headless npm run test-browser", | ||||
|     "test-browser": "run-p --race run-mastodon dev test-mastodon", | ||||
|     "test-mastodon": "run-s wait-for-mastodon-to-start wait-for-mastodon-data run-testcafe", | ||||
|     "test-browser": "run-p --race run-mastodon build-and-start test-mastodon", | ||||
|     "test-mastodon": "run-s wait-for-mastodon-to-start wait-for-mastodon-data testcafe", | ||||
|     "wait-for-mastodon-to-start": "node -r esm bin/wait-for-mastodon-to-start.js", | ||||
|     "wait-for-mastodon-data": "node -r esm bin/wait-for-mastodon-data.js", | ||||
|     "globalize-css": "node ./bin/globalize-css.js", | ||||
|  |  | |||
|  | @ -53,14 +53,13 @@ | |||
| 
 | ||||
|   export default { | ||||
|     oncreate () { | ||||
|       if (process.env.NODE_ENV !== 'production') { | ||||
|         window.__fakeFileInput = (file) => { | ||||
|           this.onFileChange({ | ||||
|             target: { | ||||
|               files: [file] | ||||
|             } | ||||
|           }) | ||||
|         } | ||||
|       // for testing | ||||
|       window.__fakeFileInput = (file) => { | ||||
|         this.onFileChange({ | ||||
|           target: { | ||||
|             files: [file] | ||||
|           } | ||||
|         }) | ||||
|       } | ||||
|     }, | ||||
|     components: { | ||||
|  |  | |||
|  | @ -58,3 +58,8 @@ observers(store) | |||
| if (process.browser && process.env.NODE_ENV !== 'production') { | ||||
|   window.store = store // for debugging
 | ||||
| } | ||||
| 
 | ||||
| // needed for tests
 | ||||
| if (process.browser) { | ||||
|   window.__forceOnline = online => store.set({online}) | ||||
| } | ||||
|  |  | |||
|  | @ -1,4 +1,3 @@ | |||
| import { Role } from 'testcafe' | ||||
| import { | ||||
|   authorizeInput, emailInput, getUrl, instanceInput, mastodonLogInButton, | ||||
|   passwordInput, | ||||
|  | @ -21,10 +20,21 @@ async function login (t, username, password) { | |||
|     .expect(getUrl()).eql('http://localhost:4002/', {timeout: 30000}) | ||||
| } | ||||
| 
 | ||||
| export const foobarRole = Role('http://localhost:4002/settings/instances/add', async t => { | ||||
|   await login(t, users.foobar.email, users.foobar.password) | ||||
| }) | ||||
| // roles appear not to be working anymore :(
 | ||||
| // export const foobarRole = Role('http://localhost:4002/settings/instances/add', async t => {
 | ||||
| //   await login(t, users.foobar.email, users.foobar.password)
 | ||||
| // })
 | ||||
| //
 | ||||
| // export const lockedAccountRole = Role('http://localhost:4002/settings/instances/add', async t => {
 | ||||
| //   await login(t, users.LockedAccount.email, users.LockedAccount.password)
 | ||||
| // })
 | ||||
| 
 | ||||
| export const lockedAccountRole = Role('http://localhost:4002/settings/instances/add', async t => { | ||||
| export async function loginAsFoobar (t) { | ||||
|   await t.navigateTo('/settings/instances/add') | ||||
|   await login(t, users.foobar.email, users.foobar.password) | ||||
| } | ||||
| 
 | ||||
| export async function loginAsLockedAccount (t) { | ||||
|   await t.navigateTo('/settings/instances/add') | ||||
|   await login(t, users.LockedAccount.email, users.LockedAccount.password) | ||||
| }) | ||||
| } | ||||
|  |  | |||
|  | @ -1,13 +1,18 @@ | |||
| import { Selector as $ } from 'testcafe' | ||||
| import { getFirstVisibleStatus, getNthStatus, getUrl, validateTimeline } from '../utils' | ||||
| import { | ||||
|   communityNavButton, | ||||
|   getFirstVisibleStatus, getNthStatus, getUrl, localTimelineNavButton, notificationsNavButton, | ||||
|   validateTimeline | ||||
| } from '../utils' | ||||
| import { homeTimeline, notifications, localTimeline, favorites } from '../fixtures' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`003-basic-timeline-spec.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('Shows the home timeline', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(0)) | ||||
|     .expect(getFirstVisibleStatus().exists).ok() | ||||
|     .expect(getFirstVisibleStatus().hasAttribute('aria-setsize')).ok() | ||||
|  | @ -19,24 +24,27 @@ test('Shows the home timeline', async t => { | |||
| }) | ||||
| 
 | ||||
| test('Shows notifications', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|     .click($('nav a[aria-label=Notifications]')) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click(notificationsNavButton) | ||||
|     .expect(getUrl()).contains('/notifications') | ||||
| 
 | ||||
|   await validateTimeline(t, notifications) | ||||
| }) | ||||
| 
 | ||||
| test('Shows the local timeline', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|     .click($('nav a[aria-label=Local]')) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click(localTimelineNavButton) | ||||
|     .expect(getUrl()).contains('/local') | ||||
| 
 | ||||
|   await validateTimeline(t, localTimeline) | ||||
| }) | ||||
| 
 | ||||
| test('Shows the federated timeline', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|     .click($('nav a[aria-label=Community]')) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click(communityNavButton) | ||||
|     .expect(getUrl()).contains('/community') | ||||
|     .click($('a').withText('Federated')) | ||||
|     .expect(getUrl()).contains('/federated') | ||||
|  | @ -45,8 +53,9 @@ test('Shows the federated timeline', async t => { | |||
| }) | ||||
| 
 | ||||
| test('Shows favorites', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|     .click($('nav a[aria-label=Community]')) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click(communityNavButton) | ||||
|     .expect(getUrl()).contains('/community') | ||||
|     .click($('a').withText('Favorites')) | ||||
|     .expect(getUrl()).contains('/favorites') | ||||
|  |  | |||
|  | @ -1,12 +1,13 @@ | |||
| import { Selector as $ } from 'testcafe' | ||||
| import { communityNavButton, getNthPinnedStatus, getUrl } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`004-pinned-statuses.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test("shows a user's pinned statuses", async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click(communityNavButton) | ||||
|     .expect(getUrl()).contains('/community') | ||||
|     .click($('a[href="/pinned"]')) | ||||
|  | @ -17,7 +18,8 @@ test("shows a user's pinned statuses", async t => { | |||
| }) | ||||
| 
 | ||||
| test("shows pinned statuses on a user's account page", async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .navigateTo('/accounts/2') | ||||
|     .expect(getNthPinnedStatus(0).getAttribute('aria-posinset')).eql('0') | ||||
|     .expect(getNthPinnedStatus(0).getAttribute('aria-setsize')).eql('1') | ||||
|  | @ -25,7 +27,8 @@ test("shows pinned statuses on a user's account page", async t => { | |||
| }) | ||||
| 
 | ||||
| test("shows pinned statuses on a user's account page 2", async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .navigateTo('/accounts/3') | ||||
|     .expect(getNthPinnedStatus(0).getAttribute('aria-posinset')).eql('0') | ||||
|     .expect(getNthPinnedStatus(0).getAttribute('aria-setsize')).eql('2') | ||||
|  |  | |||
|  | @ -1,11 +1,12 @@ | |||
| import { getNthStatus } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`005-status-types.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('shows direct vs followers-only vs regular', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(getNthStatus(1).getAttribute('aria-label')).eql('Status by admin') | ||||
|     .expect(getNthStatus(1).find('.status-content').innerText).contains('notification of unlisted message') | ||||
|     .expect(getNthStatus(1).find('.status-toolbar button:nth-child(2)').getAttribute('aria-label')) | ||||
|  | @ -24,7 +25,8 @@ test('shows direct vs followers-only vs regular', async t => { | |||
| }) | ||||
| 
 | ||||
| test('shows direct vs followers-only vs regular in notifications', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .navigateTo('/notifications') | ||||
|     .expect(getNthStatus(2).getAttribute('aria-label')).eql('Status by admin') | ||||
|     .expect(getNthStatus(2).find('.status-content').innerText).contains('notification of unlisted message') | ||||
|  |  | |||
|  | @ -1,12 +1,13 @@ | |||
| import { Selector as $ } from 'testcafe' | ||||
| import { getNthStatus } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`006-tabindex.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('shows correct tabindex in home timeline', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(getNthStatus(0).getAttribute('tabindex')).eql('0') | ||||
|     .expect(getNthStatus(1).getAttribute('tabindex')).eql('0') | ||||
|     .expect(getNthStatus(2).getAttribute('tabindex')).eql('0') | ||||
|  | @ -14,7 +15,8 @@ test('shows correct tabindex in home timeline', async t => { | |||
| }) | ||||
| 
 | ||||
| test('shows correct tabindex in notifications', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .navigateTo('/notifications') | ||||
|     .expect(getNthStatus(0).getAttribute('tabindex')).eql('0') | ||||
|     .expect(getNthStatus(1).getAttribute('tabindex')).eql('0') | ||||
|  | @ -31,7 +33,8 @@ test('shows correct tabindex in notifications', async t => { | |||
| }) | ||||
| 
 | ||||
| test('shows correct tabindex in pinned statuses', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .navigateTo('/pinned') | ||||
|     .expect($('.status-article').getAttribute('tabindex')).eql('0') | ||||
|     .expect($('.status-article').getAttribute('aria-posinset')).eql('0') | ||||
|  |  | |||
|  | @ -4,14 +4,15 @@ import { | |||
|   accountProfileFollowedBy, accountProfileName, accountProfileUsername, getUrl, | ||||
|   validateTimeline | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| import { quuxStatuses } from '../fixtures' | ||||
| 
 | ||||
| fixture`007-account-profile.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('shows account profile', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click($('.status-author-name').withText(('quux'))) | ||||
|     .expect(getUrl()).contains('/accounts/3') | ||||
|     .expect(accountProfileName.innerText).contains('quux') | ||||
|  | @ -22,7 +23,8 @@ test('shows account profile', async t => { | |||
| }) | ||||
| 
 | ||||
| test('shows account profile 2', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click($('.status-author-name').withText(('admin'))) | ||||
|     .expect(getUrl()).contains('/accounts/1') | ||||
|     .expect(accountProfileName.innerText).contains('admin') | ||||
|  | @ -33,7 +35,8 @@ test('shows account profile 2', async t => { | |||
| }) | ||||
| 
 | ||||
| test('shows account profile 3', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click($('.mention').withText(('foobar'))) | ||||
|     .expect(getUrl()).contains('/accounts/2') | ||||
|     .expect(accountProfileName.innerText).contains('foobar') | ||||
|  | @ -44,7 +47,8 @@ test('shows account profile 3', async t => { | |||
| }) | ||||
| 
 | ||||
| test('shows account profile statuses', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click($('.status-author-name').withText(('quux'))) | ||||
|     .expect(getUrl()).contains('/accounts/3') | ||||
|     .expect($('.pinned-statuses .status-article').getAttribute('aria-setsize')).eql('2') | ||||
|  |  | |||
|  | @ -1,11 +1,11 @@ | |||
| import { closeDialogButton, getNthStatus, modalDialogContents, scrollToStatus } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`008-status-media.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('shows sensitive images and videos', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await scrollToStatus(t, 7) | ||||
|   await t.expect(getNthStatus(7).find('.status-media img').exists).notOk() | ||||
|     .click(getNthStatus(7).find('.status-sensitive-media-button')) | ||||
|  | @ -18,7 +18,7 @@ test('shows sensitive images and videos', async t => { | |||
| }) | ||||
| 
 | ||||
| test('click and close image and video modals', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await scrollToStatus(t, 9) | ||||
|   await t.expect(modalDialogContents.exists).notOk() | ||||
|     .click(getNthStatus(9).find('.play-video-button')) | ||||
|  |  | |||
|  | @ -3,14 +3,15 @@ import { | |||
|   getNthStatus, getUrl, validateTimeline, scrollToBottomOfTimeline, getFirstVisibleStatus, | ||||
|   goBack, forceOffline, forceOnline, searchNavButton, searchInput, getNthSearchResult | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| import { bazThreadRelativeTo2, bazThreadRelativeTo2b, bazThreadRelativeTo2B2, quuxThread } from '../fixtures' | ||||
| 
 | ||||
| fixture`009-threads.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('Shows a thread', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click($('a').withText('quux')) | ||||
| 
 | ||||
|   await scrollToBottomOfTimeline(t) | ||||
|  | @ -24,7 +25,8 @@ test('Shows a thread', async t => { | |||
| }) | ||||
| 
 | ||||
| test('Scrolls to proper point in thread', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click($('a').withText('quux')) | ||||
|     .hover(getNthStatus(0)) | ||||
|     .hover(getNthStatus(2)) | ||||
|  | @ -69,7 +71,8 @@ async function validateForkedThread (t) { | |||
| } | ||||
| 
 | ||||
| test('Forked threads look correct online and offline', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getFirstVisibleStatus()) | ||||
|   await navigateToBazAccount(t) | ||||
|   await validateForkedThread(t) | ||||
|  |  | |||
|  | @ -3,13 +3,13 @@ import { | |||
|   goBackButton, getActiveElementInnerText, getNthReplyButton, getActiveElementInsideNthStatus, focus, | ||||
|   getNthStatusSelector | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`010-focus.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('modal preserves focus', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await scrollToStatus(t, 9) | ||||
|   // explicitly hover-focus-click
 | ||||
|   await t.hover(getNthStatus(9).find('.play-video-button')) | ||||
|  | @ -22,7 +22,7 @@ test('modal preserves focus', async t => { | |||
| }) | ||||
| 
 | ||||
| test('timeline preserves focus', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   // explicitly hover-focus-click
 | ||||
|   await t.hover(getNthStatus(0)) | ||||
|   await focus(getNthStatusSelector(0))() | ||||
|  | @ -36,7 +36,8 @@ test('timeline preserves focus', async t => { | |||
| }) | ||||
| 
 | ||||
| test('timeline link preserves focus', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(getNthStatus(0).exists).ok({timeout: 20000}) | ||||
|     .click(getNthStatus(0).find('.status-header a')) | ||||
|     .expect(getUrl()).contains('/accounts/') | ||||
|  | @ -53,7 +54,8 @@ test('timeline link preserves focus', async t => { | |||
| }) | ||||
| 
 | ||||
| test('notification timeline preserves focus', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .navigateTo('/notifications') | ||||
|   await scrollToStatus(t, 5) | ||||
|   await t.click(getNthStatus(5).find('.status-header a')) | ||||
|  | @ -66,7 +68,8 @@ test('notification timeline preserves focus', async t => { | |||
| }) | ||||
| 
 | ||||
| test('thread preserves focus', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .navigateTo('/accounts/3') | ||||
|   await scrollToStatus(t, 2) | ||||
|   await t.click(getNthStatus(2)) | ||||
|  | @ -87,7 +90,8 @@ test('thread preserves focus', async t => { | |||
| }) | ||||
| 
 | ||||
| test('reply preserves focus and moves focus to the text input', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(getNthStatus(1).exists).ok({timeout: 20000}) | ||||
|     .click(getNthReplyButton(1)) | ||||
|     .expect(getActiveElementClass()).contains('compose-box-input') | ||||
|  |  | |||
|  | @ -3,13 +3,14 @@ import { | |||
|   favoritesCountElement, getFavoritesCount, getNthStatus, getReblogsCount, getUrl, | ||||
|   reblogsCountElement | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`011-reblog-favorites-count.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('shows favorites', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click(getNthStatus(0)) | ||||
|     .expect(getUrl()).contains('/statuses/') | ||||
|     .expect(getFavoritesCount()).eql(2) | ||||
|  | @ -24,7 +25,8 @@ test('shows favorites', async t => { | |||
| }) | ||||
| 
 | ||||
| test('shows boosts', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click(getNthStatus(0)) | ||||
|     .expect(getUrl()).contains('/statuses/') | ||||
|     .expect(getReblogsCount()).eql(1) | ||||
|  |  | |||
|  | @ -5,13 +5,14 @@ import { | |||
|   notificationsNavButton, | ||||
|   times | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`012-compose.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('shows compose limits', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(composeInput) | ||||
|     .expect(composeLengthIndicator.innerText).eql('500') | ||||
|     .expect(composeButton.hasAttribute('disabled')).notOk() | ||||
|  | @ -37,7 +38,8 @@ test('shows compose limits', async t => { | |||
| }) | ||||
| 
 | ||||
| test('shows compose limits for URLs/handles', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(composeLengthIndicator.innerText).eql('500') | ||||
|     .expect(composeButton.hasAttribute('disabled')).notOk() | ||||
|     .typeText(composeInput, 'hello world ' + | ||||
|  | @ -48,14 +50,16 @@ test('shows compose limits for URLs/handles', async t => { | |||
| }) | ||||
| 
 | ||||
| test('shows compose limits for emoji', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .typeText(composeInput, 'hello world \ud83c\ude01 \ud83d\udc6a') | ||||
|     .expect(composeLengthIndicator.innerText).eql('485') | ||||
|     .expect(composeButton.hasAttribute('disabled')).notOk() | ||||
| }) | ||||
| 
 | ||||
| test('shows compose limits for custom emoji', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .typeText(composeInput, 'hello world ') | ||||
|     .click(emojiButton) | ||||
|     .click($('button img[title=":blobnom:"]')) | ||||
|  | @ -64,7 +68,8 @@ test('shows compose limits for custom emoji', async t => { | |||
| }) | ||||
| 
 | ||||
| test('inserts custom emoji correctly', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .typeText(composeInput, 'hello world') | ||||
|     .selectText(composeInput, 6, 6) | ||||
|     .expect(getComposeSelectionStart()).eql(6) | ||||
|  | @ -83,7 +88,8 @@ test('inserts custom emoji correctly', async t => { | |||
| }) | ||||
| 
 | ||||
| test('inserts emoji without typing anything', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click(emojiButton) | ||||
|     .click($('button img[title=":blobpats:"]')) | ||||
|     .expect(composeInput.value).eql(':blobpats: ') | ||||
|  |  | |||
|  | @ -2,13 +2,14 @@ import { | |||
|   composeInput, getNthDeleteMediaButton, getNthMedia, mediaButton, | ||||
|   uploadKittenImage | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`013-compose-media.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('inserts media', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(mediaButton.hasAttribute('disabled')).notOk() | ||||
|   await (uploadKittenImage(1)()) | ||||
|   await t.expect(getNthMedia(1).getAttribute('alt')).eql('kitten1.jpg') | ||||
|  | @ -35,7 +36,8 @@ test('inserts media', async t => { | |||
| }) | ||||
| 
 | ||||
| test('removes media', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(mediaButton.exists).ok() | ||||
|   await (uploadKittenImage(1)()) | ||||
|   await t.expect(getNthMedia(1).getAttribute('alt')).eql('kitten1.jpg') | ||||
|  | @ -50,7 +52,8 @@ test('removes media', async t => { | |||
| }) | ||||
| 
 | ||||
| test('changes URLs as media is added/removed', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(mediaButton.exists).ok() | ||||
|   await (uploadKittenImage(1)()) | ||||
|   await t.expect(composeInput.value).match(/^ http:\/\/localhost:3000\/media\/\S+$/) | ||||
|  |  | |||
|  | @ -1,11 +1,12 @@ | |||
| import { getNthPostPrivacyOptionInDialog, postPrivacyButton } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`014-compose-post-privacy.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('Changes post privacy', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(postPrivacyButton.getAttribute('aria-label')).eql('Adjust privacy (currently Public)') | ||||
|     .click(postPrivacyButton) | ||||
|     .click(getNthPostPrivacyOptionInDialog(2)) | ||||
|  |  | |||
|  | @ -2,13 +2,14 @@ import { | |||
|   composeContentWarning, composeInput, composeLengthIndicator, contentWarningButton, homeNavButton, | ||||
|   notificationsNavButton | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`015-compose-content-warnings.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('Changes content warnings', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(composeContentWarning.exists).notOk() | ||||
|     .expect(contentWarningButton.getAttribute('aria-label')).eql('Add content warning') | ||||
|     .expect(contentWarningButton.getAttribute('aria-pressed')).eql('false') | ||||
|  | @ -37,7 +38,8 @@ test('Changes content warnings', async t => { | |||
| }) | ||||
| 
 | ||||
| test('Considers content warnings for length limits', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(composeLengthIndicator.innerText).eql('500') | ||||
|     .click(contentWarningButton) | ||||
|     .typeText(composeContentWarning, 'my content warning', {paste: true}) | ||||
|  | @ -53,7 +55,8 @@ test('Considers content warnings for length limits', async t => { | |||
| }) | ||||
| 
 | ||||
| test('Content warning goes away if you hide it', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click(contentWarningButton) | ||||
|     .expect(composeContentWarning.value).eql('') | ||||
|     .typeText(composeContentWarning, 'yo', {paste: true}) | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import { getNthStatus, getUrl } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| import { Selector as $ } from 'testcafe' | ||||
| 
 | ||||
| fixture`016-external-links.js` | ||||
|  | @ -14,7 +14,8 @@ function getAnchorInProfile (n) { | |||
| } | ||||
| 
 | ||||
| test('converts external links in statuses', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(0)) | ||||
|     .navigateTo('/accounts/4') | ||||
|     .expect(getUrl()).contains('/accounts/4') | ||||
|  | @ -31,7 +32,8 @@ test('converts external links in statuses', async t => { | |||
| }) | ||||
| 
 | ||||
| test('converts external links in profiles', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(0)) | ||||
|     .navigateTo('/accounts/4') | ||||
|     .expect(getUrl()).contains('/accounts/4') | ||||
|  |  | |||
|  | @ -5,13 +5,14 @@ import { | |||
|   getNthReplyContentWarningInput, getNthReplyPostPrivacyButton, | ||||
|   getNthStatus, getUrl, homeNavButton, notificationsNavButton, scrollToStatus | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`017-compose-reply.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('account handle populated correctly for replies', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click(getNthReplyButton(0)) | ||||
|     .expect(getNthComposeReplyInput(0).value).eql('@quux ') | ||||
|     .typeText(getNthComposeReplyInput(0), 'hello quux', {paste: true}) | ||||
|  | @ -29,7 +30,8 @@ test('account handle populated correctly for replies', async t => { | |||
| }) | ||||
| 
 | ||||
| test('replying to posts with mentions', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click(getNthReplyButton(1)) | ||||
|     .expect(getNthComposeReplyInput(1).value).eql('@admin ') | ||||
|     .navigateTo('/accounts/4') | ||||
|  | @ -38,7 +40,8 @@ test('replying to posts with mentions', async t => { | |||
| }) | ||||
| 
 | ||||
| test('replies have same privacy as replied-to status by default', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(0)) | ||||
|     .hover(getNthStatus(1)) | ||||
|     .click(getNthReplyButton(1)) | ||||
|  | @ -62,7 +65,7 @@ test('replies have same privacy as replied-to status by default', async t => { | |||
| }) | ||||
| 
 | ||||
| test('replies have same CW as replied-to status', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await scrollToStatus(t, 7) | ||||
|   await t.click(getNthReplyButton(7)) | ||||
|     .expect(getNthReplyContentWarningInput(7).value).eql('kitten CW') | ||||
|  | @ -72,7 +75,7 @@ test('replies have same CW as replied-to status', async t => { | |||
| }) | ||||
| 
 | ||||
| test('replies save deletions of CW', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await scrollToStatus(t, 7) | ||||
|   await t.click(getNthReplyButton(7)) | ||||
|     .expect(getNthReplyContentWarningInput(7).value).eql('kitten CW') | ||||
|  | @ -84,7 +87,7 @@ test('replies save deletions of CW', async t => { | |||
| }) | ||||
| 
 | ||||
| test('replies save changes to CW', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await scrollToStatus(t, 7) | ||||
|   await t.click(getNthReplyButton(7)) | ||||
|     .expect(getNthReplyContentWarningInput(7).value).eql('kitten CW') | ||||
|  | @ -96,7 +99,8 @@ test('replies save changes to CW', async t => { | |||
| }) | ||||
| 
 | ||||
| test('replies save changes to post privacy', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(0)) | ||||
|     .hover(getNthStatus(1)) | ||||
|     .click(getNthReplyButton(1)) | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ import { | |||
|   composeInput, getNthAutosuggestionResult, getNthComposeReplyInput, getNthReplyButton, getNthStatus, sleep | ||||
| } from '../utils' | ||||
| import { Selector as $ } from 'testcafe' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`018-compose-autosuggest.js` | ||||
|   .page`http://localhost:4002` | ||||
|  | @ -10,7 +10,8 @@ fixture`018-compose-autosuggest.js` | |||
| const timeout = 30000 | ||||
| 
 | ||||
| test('autosuggests user handles', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(composeInput) | ||||
|   await sleep(1000) | ||||
|   await t | ||||
|  | @ -31,7 +32,8 @@ test('autosuggests user handles', async t => { | |||
| }) | ||||
| 
 | ||||
| test('autosuggests custom emoji', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(composeInput) | ||||
|     .typeText(composeInput, ':blob') | ||||
|     .click(getNthAutosuggestionResult(1)) | ||||
|  | @ -51,7 +53,8 @@ test('autosuggests custom emoji', async t => { | |||
| }) | ||||
| 
 | ||||
| test('autosuggest custom emoji works with regular emoji - keyboard', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(composeInput) | ||||
|     .typeText(composeInput, '\ud83c\udf4d :blobno') | ||||
|     .expect(getNthAutosuggestionResult(1).innerText).contains(':blobnom:', {timeout}) | ||||
|  | @ -60,7 +63,8 @@ test('autosuggest custom emoji works with regular emoji - keyboard', async t => | |||
| }) | ||||
| 
 | ||||
| test('autosuggest custom emoji works with regular emoji - clicking', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(composeInput) | ||||
|     .typeText(composeInput, '\ud83c\udf4d :blobno') | ||||
|     .expect(getNthAutosuggestionResult(1).innerText).contains(':blobnom:', {timeout}) | ||||
|  | @ -69,7 +73,8 @@ test('autosuggest custom emoji works with regular emoji - clicking', async t => | |||
| }) | ||||
| 
 | ||||
| test('autosuggest handles works with regular emoji - keyboard', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(composeInput) | ||||
|     .typeText(composeInput, '\ud83c\udf4d @quu') | ||||
|     .expect(getNthAutosuggestionResult(1).innerText).contains('@quux', {timeout}) | ||||
|  | @ -78,7 +83,8 @@ test('autosuggest handles works with regular emoji - keyboard', async t => { | |||
| }) | ||||
| 
 | ||||
| test('autosuggest handles works with regular emoji - clicking', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(composeInput) | ||||
|     .typeText(composeInput, '\ud83c\udf4d @quu') | ||||
|     .expect(getNthAutosuggestionResult(1).innerText).contains('@quux', {timeout}) | ||||
|  | @ -87,7 +93,8 @@ test('autosuggest handles works with regular emoji - clicking', async t => { | |||
| }) | ||||
| 
 | ||||
| test('autosuggest only shows for one input', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(composeInput) | ||||
|     .typeText(composeInput, '@quu') | ||||
|     .hover(getNthStatus(0)) | ||||
|  | @ -99,7 +106,8 @@ test('autosuggest only shows for one input', async t => { | |||
| }) | ||||
| 
 | ||||
| test('autosuggest only shows for one input part 2', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(composeInput) | ||||
|     .typeText(composeInput, '@adm') | ||||
|     .expect($('.compose-autosuggest.shown').exists).ok({timeout}) | ||||
|  |  | |||
|  | @ -2,13 +2,14 @@ import { | |||
|   accountProfileMoreOptionsButton, closeDialogButton, | ||||
|   getNthDialogOptionsOption, modalDialog | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`019-mention.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('can mention from account profile', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .navigateTo('/accounts/5') | ||||
|     .click(accountProfileMoreOptionsButton) | ||||
|     .expect(getNthDialogOptionsOption(1).innerText).contains('Mention @baz') | ||||
|  |  | |||
|  | @ -1,14 +1,15 @@ | |||
| import { | ||||
|   settingsNavButton | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| import { Selector as $ } from 'testcafe' | ||||
| 
 | ||||
| fixture`020-themes.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('can set a theme', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click(settingsNavButton) | ||||
|     .click($('a[href="/settings/instances"]')) | ||||
|     .click($('a[href="/settings/instances/localhost:3000"]')) | ||||
|  |  | |||
|  | @ -3,13 +3,14 @@ import { | |||
|   followsButton, getNthSearchResult, | ||||
|   getNthStatus, getUrl, goBack | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`021-followers-follows.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('shows followers and follows', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click(getNthStatus(0).find('.status-author-name')) | ||||
|     .expect(getUrl()).match(/\/accounts\/3$/) | ||||
|     .expect(followsButton.getAttribute('aria-label')).eql('Follows 2') | ||||
|  |  | |||
|  | @ -3,13 +3,14 @@ import { | |||
|   getNthFavoriteButton, getNthFavorited, getNthStatus, getUrl, homeNavButton, notificationsNavButton, | ||||
|   scrollToBottomOfTimeline, scrollToTopOfTimeline | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`100-favorite-unfavorite.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('favorites a status', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(4)) | ||||
|     .expect(getNthFavorited(4)).eql('false') | ||||
|     .click(getNthFavoriteButton(4)) | ||||
|  | @ -35,7 +36,8 @@ test('favorites a status', async t => { | |||
| }) | ||||
| 
 | ||||
| test('unfavorites a status', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(getNthFavorited(1)).eql('true') | ||||
|     .click(getNthFavoriteButton(1)) | ||||
|     .expect(getNthFavorited(1)).eql('false') | ||||
|  | @ -56,7 +58,8 @@ test('unfavorites a status', async t => { | |||
| }) | ||||
| 
 | ||||
| test('Keeps the correct favorites count', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(4)) | ||||
|     .click(getNthFavoriteButton(4)) | ||||
|     .expect(getNthFavorited(4)).eql('true') | ||||
|  |  | |||
|  | @ -3,13 +3,14 @@ import { | |||
|   notificationsNavButton, | ||||
|   scrollToBottomOfTimeline, scrollToTopOfTimeline | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`101-reblog-unreblog.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('reblogs a status', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(0)) | ||||
|     .expect(getNthReblogged(0)).eql('false') | ||||
|     .click(getNthReblogButton(0)) | ||||
|  | @ -34,7 +35,8 @@ test('reblogs a status', async t => { | |||
| }) | ||||
| 
 | ||||
| test('unreblogs a status', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(4)) | ||||
|     .expect(getNthReblogged(4)).eql('false') | ||||
|     .click(getNthReblogButton(4)) | ||||
|  | @ -59,7 +61,8 @@ test('unreblogs a status', async t => { | |||
| }) | ||||
| 
 | ||||
| test('Keeps the correct reblogs count', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(4)) | ||||
|     .expect(getNthReblogged(4)).eql('true') | ||||
|     .click(getNthStatus(4)) | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| import { getNthStatus, getUrl, homeNavButton, notificationsNavButton, validateTimeline } from '../utils' | ||||
| import { favoriteStatusAs } from '../serverActions' | ||||
| import { notifications } from '../fixtures' | ||||
|  | @ -7,7 +7,8 @@ fixture`102-notifications.js` | |||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('shows unread notifications', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(0)) | ||||
|     .hover(getNthStatus(2)) | ||||
|     .hover(getNthStatus(4)) | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| import { | ||||
|   composeInput, getNthComposeReplyButton, getNthComposeReplyInput, getNthReplyButton, getNthStatus, getUrl, | ||||
|   homeNavButton, notificationsNavButton, | ||||
|  | @ -9,7 +9,8 @@ fixture`103-compose.js` | |||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('statuses show up in home timeline', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .typeText(composeInput, 'hello world', {paste: true}) | ||||
|     .click(postStatusButton) | ||||
|     .expect(getNthStatus(0).innerText).contains('hello world') | ||||
|  | @ -23,7 +24,8 @@ test('statuses show up in home timeline', async t => { | |||
| }) | ||||
| 
 | ||||
| test('statuses in threads show up in right order', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .navigateTo('/accounts/5') | ||||
|     .click(getNthStatus(2)) | ||||
|     .expect(getUrl()).contains('/statuses') | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| import { | ||||
|   getNthStatus, scrollContainerToTop, showMoreButton, sleep | ||||
| } from '../utils' | ||||
|  | @ -8,14 +8,16 @@ fixture`104-streaming.js` | |||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('new incoming statuses show up immediately', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(0)) | ||||
|   await postAs('admin', 'hello my baby hello my honey') | ||||
|   await t.expect(getNthStatus(0).innerText).contains('hello my baby hello my honey') | ||||
| }) | ||||
| 
 | ||||
| test('new incoming toots show a button if scrolled down', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(0)) | ||||
|     .hover(getNthStatus(2)) | ||||
|     .hover(getNthStatus(4)) | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| import { | ||||
|   clickToNotificationsAndBackHome, forceOffline, forceOnline, getNthStatus, getUrl, homeNavButton, | ||||
|   notificationsNavButton | ||||
|  | @ -10,7 +10,8 @@ fixture`105-deletes.js` | |||
| 
 | ||||
| test('deleted statuses are removed from the timeline', async t => { | ||||
|   let timeout = 20000 | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(0)) | ||||
|   let status = await postAs('admin', "I'm gonna delete this") | ||||
|   await t.expect(getNthStatus(0).innerText).contains("I'm gonna delete this", {timeout}) | ||||
|  | @ -30,7 +31,8 @@ test('deleted statuses are removed from the timeline', async t => { | |||
| 
 | ||||
| test('deleted statuses are removed from threads', async t => { | ||||
|   let timeout = 20000 | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(0)) | ||||
|   let status = await postAs('admin', "I won't delete this") | ||||
|   let reply = await postReplyAs('admin', 'But I will delete this', status.id) | ||||
|  | @ -54,7 +56,8 @@ test('deleted statuses are removed from threads', async t => { | |||
| 
 | ||||
| test('deleted statuses result in deleted notifications', async t => { | ||||
|   let timeout = 20000 | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .hover(getNthStatus(0)) | ||||
|     .expect(notificationsNavButton.getAttribute('aria-label')).eql('Notifications') | ||||
|   let status = await postAs('admin', "@foobar yo yo foobar what's up") | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| import { | ||||
|   accountProfileFollowButton, | ||||
|   getNthStatus, | ||||
|  | @ -12,7 +12,8 @@ fixture`106-follow-requests.js` | |||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('can request to follow an account', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .navigateTo('/accounts/6') | ||||
|     .expect(accountProfileFollowButton.getAttribute('aria-label')).eql('Follow') | ||||
|     .click(accountProfileFollowButton) | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| import { | ||||
|   getNthStatus, homeNavButton, localTimelineNavButton, sleep | ||||
| } from '../utils' | ||||
|  | @ -12,7 +12,8 @@ fixture`107-streaming-gap.js` | |||
| test('fills in a status posted while away from timeline', async t => { | ||||
|   let timeout = 30000 | ||||
| 
 | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click(localTimelineNavButton) | ||||
|     .expect(getNthStatus(0).exists).ok({timeout}) | ||||
|     .hover(getNthStatus(0)) | ||||
|  |  | |||
|  | @ -2,14 +2,14 @@ import { | |||
|   composeButton, getNthStatus, scrollToStatus, modalDialog, sleep, | ||||
|   notificationsNavButton, getUrl | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| import { Selector as $ } from 'testcafe' | ||||
| 
 | ||||
| fixture`108-compose-dialog.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('can compose using a dialog', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await scrollToStatus(t, 15) | ||||
|   await t.expect(modalDialog.exists).notOk() | ||||
|     .expect(composeButton.getAttribute('aria-label')).eql('Compose') | ||||
|  | @ -27,7 +27,7 @@ test('can compose using a dialog', async t => { | |||
| }) | ||||
| 
 | ||||
| test('can use emoji dialog within compose dialog', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await scrollToStatus(t, 15) | ||||
|   await t.expect(composeButton.getAttribute('aria-label')).eql('Compose') | ||||
|   await sleep(2000) | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import { | |||
|   mediaButton, notificationsNavButton, | ||||
|   uploadKittenImage | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`109-compose-media.js` | ||||
|   .page`http://localhost:4002` | ||||
|  | @ -18,7 +18,8 @@ async function uploadTwoKittens (t) { | |||
| } | ||||
| 
 | ||||
| test('uploads alts for media', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(mediaButton.hasAttribute('disabled')).notOk() | ||||
|   await uploadTwoKittens(t) | ||||
|   await t.typeText(getNthMediaAltInput(2), 'kitten 2') | ||||
|  | @ -31,7 +32,8 @@ test('uploads alts for media', async t => { | |||
| }) | ||||
| 
 | ||||
| test('uploads alts when deleting and re-uploading media', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(mediaButton.hasAttribute('disabled')).notOk() | ||||
|   await (uploadKittenImage(1)()) | ||||
|   await t.typeText(getNthMediaAltInput(1), 'this will be deleted') | ||||
|  | @ -46,7 +48,8 @@ test('uploads alts when deleting and re-uploading media', async t => { | |||
| }) | ||||
| 
 | ||||
| test('uploads alts mixed with no-alts', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(mediaButton.hasAttribute('disabled')).notOk() | ||||
|   await uploadTwoKittens(t) | ||||
|   await t.typeText(getNthMediaAltInput(2), 'kitten numero dos') | ||||
|  | @ -56,7 +59,8 @@ test('uploads alts mixed with no-alts', async t => { | |||
| }) | ||||
| 
 | ||||
| test('saves alts to local storage', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(mediaButton.hasAttribute('disabled')).notOk() | ||||
|   await uploadTwoKittens(t) | ||||
|   await t.typeText(getNthMediaAltInput(1), 'kitten numero uno') | ||||
|  |  | |||
|  | @ -2,13 +2,14 @@ import { | |||
|   composeButton, composeContentWarning, composeInput, contentWarningButton, | ||||
|   getNthShowOrHideButton, getNthStatus | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`110-compose-content-warnings.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('content warnings are posted', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .typeText(composeInput, 'hello this is a toot', {paste: true}) | ||||
|     .click(contentWarningButton) | ||||
|     .typeText(composeContentWarning, 'CW', {paste: true}) | ||||
|  | @ -21,7 +22,8 @@ test('content warnings are posted', async t => { | |||
| }) | ||||
| 
 | ||||
| test('content warnings are not posted if removed', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .typeText(composeInput, 'hi this is another toot', {paste: true}) | ||||
|     .click(contentWarningButton) | ||||
|     .typeText(composeContentWarning, 'content warning!', {paste: true}) | ||||
|  | @ -34,7 +36,8 @@ test('content warnings are not posted if removed', async t => { | |||
| }) | ||||
| 
 | ||||
| test('content warnings can have emoji', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .typeText(composeInput, 'I can: :blobnom:') | ||||
|     .click(contentWarningButton) | ||||
|     .typeText(composeContentWarning, 'can you feel the :blobpats: tonight') | ||||
|  | @ -48,7 +51,8 @@ test('content warnings can have emoji', async t => { | |||
| test('no XSS in content warnings or text', async t => { | ||||
|   let pwned1 = `<script>alert("pwned!")</script>` | ||||
|   let pwned2 = `<script>alert("pwned from CW!")</script>` | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .typeText(composeInput, pwned1) | ||||
|     .click(contentWarningButton) | ||||
|     .typeText(composeContentWarning, pwned2) | ||||
|  |  | |||
|  | @ -4,13 +4,14 @@ import { | |||
|   getNthComposeReplyInput, getNthReplyButton, | ||||
|   getNthStatus | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`111-focus.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('replying to a toot returns focus to reply button', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .typeText(composeInput, 'I would like, if I may, to take you on a strange journey', {paste: true}) | ||||
|     .pressKey('ctrl+enter') | ||||
|     .expect(getNthStatus(0).find('.status-content').innerText).contains('I would like, if I may, to take you on a strange journey') | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ import { | |||
|   composeInput, | ||||
|   getNthStatus | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`112-status-links.js` | ||||
|   .page`http://localhost:4002` | ||||
|  | @ -16,7 +16,8 @@ test('External links, hashtags, and mentions have correct attributes', async t = | |||
| 
 | ||||
|   const nthAnchor = n => getNthStatus(0).find('.status-content a').nth(n) | ||||
| 
 | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .typeText(composeInput, text, {paste: true}) | ||||
|     .click(composeButton) | ||||
|     .expect(getNthStatus(0).innerText).contains('Why hello there', {timeout: 20000}) | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import { | |||
|   getNthStatus, getNthStatusOptionsButton, getNthDialogOptionsOption, getUrl, modalDialog | ||||
| } from '../utils' | ||||
| import { Selector as $ } from 'testcafe' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| import { postAs } from '../serverActions' | ||||
| 
 | ||||
| fixture`113-block-unblock.js` | ||||
|  | @ -13,7 +13,8 @@ fixture`113-block-unblock.js` | |||
| test('Can block and unblock an account from a status', async t => { | ||||
|   let post = 'a very silly statement that should probably get me blocked' | ||||
|   await postAs('admin', post) | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .expect(getNthStatus(0).innerText).contains(post, {timeout: 30000}) | ||||
|     .click(getNthStatusOptionsButton(0)) | ||||
|     .expect(getNthDialogOptionsOption(1).innerText).contains('Unfollow @admin') | ||||
|  | @ -35,7 +36,8 @@ test('Can block and unblock an account from a status', async t => { | |||
| }) | ||||
| 
 | ||||
| test('Can block and unblock an account from the account profile page', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .navigateTo('/accounts/5') | ||||
|     .expect(accountProfileFollowButton.getAttribute('aria-label')).eql('Follow') | ||||
|     .click(accountProfileMoreOptionsButton) | ||||
|  |  | |||
|  | @ -4,14 +4,14 @@ import { | |||
|   getNthStatus, getNthStatusOptionsButton, getNthDialogOptionsOption, getUrl, modalDialog, closeDialogButton | ||||
| } from '../utils' | ||||
| import { Selector as $ } from 'testcafe' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| import { postAs } from '../serverActions' | ||||
| 
 | ||||
| fixture`114-mute-unmute.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('Can mute and unmute an account', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   let post = 'blah blah blah' | ||||
|   await postAs('admin', post) | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,13 +3,14 @@ import { | |||
|   accountProfileMoreOptionsButton, closeDialogButton, | ||||
|   getNthDialogOptionsOption | ||||
| } from '../utils' | ||||
| import { foobarRole } from '../roles' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| 
 | ||||
| fixture`115-follow-unfollow.js` | ||||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('Can follow and unfollow an account from the profile page', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .navigateTo('/accounts/5') | ||||
|     .expect(accountProfileFollowButton.getAttribute('aria-label')).eql('Follow') | ||||
|     .click(accountProfileMoreOptionsButton) | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { lockedAccountRole } from '../roles' | ||||
| import { loginAsLockedAccount } from '../roles' | ||||
| import { followAs, unfollowAs } from '../serverActions' | ||||
| import { | ||||
|   avatarInComposeBox, | ||||
|  | @ -14,7 +14,7 @@ fixture`116-follow-requests.js` | |||
| const timeout = 30000 | ||||
| 
 | ||||
| test('Can approve and reject follow requests', async t => { | ||||
|   await t.useRole(lockedAccountRole) | ||||
|   await loginAsLockedAccount(t) | ||||
| 
 | ||||
|   // necessary for re-running this test in local testing
 | ||||
|   await Promise.all([ | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| import { foobarRole } from '../roles' | ||||
| import { postAs } from '../serverActions' | ||||
| import { loginAsFoobar } from '../roles' | ||||
| import { | ||||
|   avatarInComposeBox, getNthDialogOptionsOption, getNthPinnedStatus, getNthPinnedStatusFavoriteButton, getNthStatus, | ||||
|   getNthStatusOptionsButton, getUrl, sleep | ||||
|   avatarInComposeBox, composeInput, getNthDialogOptionsOption, getNthPinnedStatus, getNthPinnedStatusFavoriteButton, | ||||
|   getNthStatus, | ||||
|   getNthStatusOptionsButton, getUrl, postStatusButton | ||||
| } from '../utils' | ||||
| import { users } from '../users' | ||||
| 
 | ||||
|  | @ -10,13 +10,12 @@ fixture`117-pin-unpin.js` | |||
|   .page`http://localhost:4002` | ||||
| 
 | ||||
| test('Can pin statuses', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
| 
 | ||||
|   await postAs('foobar', 'I am going to pin this') | ||||
| 
 | ||||
|   await sleep(2000) | ||||
| 
 | ||||
|   await t.click(avatarInComposeBox) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .typeText(composeInput, 'I am going to pin this', {paste: true}) | ||||
|     .click(postStatusButton) | ||||
|     .expect(getNthStatus(0).innerText).contains('I am going to pin this') | ||||
|     .click(avatarInComposeBox) | ||||
|     .expect(getUrl()).contains(`/accounts/${users.foobar.id}`) | ||||
|     .expect(getNthPinnedStatus(0).getAttribute('aria-setsize')).eql('1') | ||||
|     .expect(getNthPinnedStatus(0).innerText).contains('this is unlisted') | ||||
|  | @ -40,7 +39,8 @@ test('Can pin statuses', async t => { | |||
| }) | ||||
| 
 | ||||
| test('Can favorite a pinned status', async t => { | ||||
|   await t.useRole(foobarRole) | ||||
|   await loginAsFoobar(t) | ||||
|   await t | ||||
|     .click(avatarInComposeBox) | ||||
|     .expect(getNthPinnedStatus(0).getAttribute('aria-setsize')).eql('1') | ||||
|     .expect(getNthPinnedStatusFavoriteButton(0).getAttribute('aria-pressed')).eql('false') | ||||
|  |  | |||
|  | @ -2,8 +2,6 @@ import { ClientFunction as exec, Selector as $ } from 'testcafe' | |||
| import * as images from './images' | ||||
| import * as blobUtils from './blobUtils' | ||||
| 
 | ||||
| const SCROLL_INTERVAL = 1 | ||||
| 
 | ||||
| export const settingsButton = $('nav a[aria-label=Settings]') | ||||
| export const instanceInput = $('#instanceInput') | ||||
| export const modalDialog = $('.modal-dialog') | ||||
|  | @ -80,9 +78,9 @@ export const getActiveElementInsideNthStatus = exec(() => { | |||
| 
 | ||||
| export const goBack = exec(() => window.history.back()) | ||||
| 
 | ||||
| export const forceOffline = exec(() => window.store.set({online: false})) | ||||
| export const forceOffline = exec(() => window.__forceOnline(false)) | ||||
| 
 | ||||
| export const forceOnline = exec(() => window.store.set({online: true})) | ||||
| export const forceOnline = exec(() => window.__forceOnline(true)) | ||||
| 
 | ||||
| export const getComposeSelectionStart = exec(() => composeInput().selectionStart, { | ||||
|   dependencies: { composeInput } | ||||
|  | @ -157,6 +155,18 @@ export function getNthStatusSelector (n) { | |||
|   return `div[aria-hidden="false"] > article[aria-posinset="${n}"]` | ||||
| } | ||||
| 
 | ||||
| export function getNthStatusContent (n) { | ||||
|   return $(`${getNthStatusSelector(n)} .status-content`) | ||||
| } | ||||
| 
 | ||||
| export function getNthStatusSpoiler (n) { | ||||
|   return $(`${getNthStatusSelector(n)} .status-spoiler`) | ||||
| } | ||||
| 
 | ||||
| export function getNthStatusHeader (n) { | ||||
|   return $(`${getNthStatusSelector(n)} .status-header`) | ||||
| } | ||||
| 
 | ||||
| export function getNthStatusAndImage (nStatus, nImage) { | ||||
|   return getNthStatus(nStatus).find(`.status-media .show-image-button:nth-child(${nImage + 1}) img`) | ||||
| } | ||||
|  | @ -234,36 +244,31 @@ export function getNthPinnedStatusFavoriteButton (n) { | |||
| } | ||||
| 
 | ||||
| export async function validateTimeline (t, timeline) { | ||||
|   const timeout = 20000 | ||||
|   const timeout = 30000 | ||||
|   for (let i = 0; i < timeline.length; i++) { | ||||
|     let status = timeline[i] | ||||
|     await t.expect(getNthStatus(i).exists).ok({ timeout }) | ||||
|     // hovering forces TestCafé to scroll to that element: https://git.io/vABV2
 | ||||
|     await t.hover(getNthStatus(i)) | ||||
|     if (status.content) { | ||||
|       await t.expect(getNthStatus(i).find('.status-content p').innerText) | ||||
|       await t.expect(getNthStatusContent(i).innerText) | ||||
|         .contains(status.content, { timeout }) | ||||
|     } | ||||
|     if (status.spoiler) { | ||||
|       await t.expect(getNthStatus(i).find('.status-spoiler p').innerText) | ||||
|       await t.expect(getNthStatusSpoiler(i).innerText) | ||||
|         .contains(status.spoiler, { timeout }) | ||||
|     } | ||||
|     if (status.followedBy) { | ||||
|       await t.expect(getNthStatus(i).find('.status-header span').innerText) | ||||
|       await t.expect(getNthStatusHeader(i).innerText) | ||||
|         .contains(status.followedBy + ' followed you', { timeout }) | ||||
|     } | ||||
|     if (status.rebloggedBy) { | ||||
|       await t.expect(getNthStatus(i).find('.status-header span').innerText) | ||||
|       await t.expect(getNthStatusHeader(i).innerText) | ||||
|         .contains(status.rebloggedBy + ' boosted your status', { timeout }) | ||||
|     } | ||||
|     if (status.favoritedBy) { | ||||
|       await t.expect(getNthStatus(i).find('.status-header span').innerText) | ||||
|       await t.expect(getNthStatusHeader(i).innerText) | ||||
|         .contains(status.favoritedBy + ' favorited your status', { timeout }) | ||||
|     } | ||||
| 
 | ||||
|     // hovering forces TestCafé to scroll to that element: https://git.io/vABV2
 | ||||
|     if (i % SCROLL_INTERVAL === (SCROLL_INTERVAL - 1)) { // only scroll every nth element
 | ||||
|       await t.hover(getNthStatus(i)) | ||||
|         .expect($('.loading-footer').exist).notOk() | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | @ -272,8 +277,7 @@ export async function scrollToTopOfTimeline (t) { | |||
|   while (true) { | ||||
|     await t.hover(getNthStatus(i)) | ||||
|       .expect($('.loading-footer').exist).notOk() | ||||
|     i -= SCROLL_INTERVAL | ||||
|     if (i <= 0) { | ||||
|     if (--i <= 0) { | ||||
|       break | ||||
|     } | ||||
|   } | ||||
|  | @ -285,8 +289,7 @@ export async function scrollToBottomOfTimeline (t) { | |||
|     await t.hover(getNthStatus(i)) | ||||
|       .expect($('.loading-footer').exist).notOk() | ||||
|     let size = await getNthStatus(i).getAttribute('aria-setsize') | ||||
|     i += SCROLL_INTERVAL | ||||
|     if (i >= size - 1) { | ||||
|     if (++i >= size - 1) { | ||||
|       break | ||||
|     } | ||||
|   } | ||||
|  | @ -294,7 +297,7 @@ export async function scrollToBottomOfTimeline (t) { | |||
| 
 | ||||
| export async function scrollToStatus (t, n) { | ||||
|   let timeout = 20000 | ||||
|   for (let i = 0; i <= n; i += SCROLL_INTERVAL) { | ||||
|   for (let i = 0; i <= n; i++) { | ||||
|     await t.expect(getNthStatus(i).exists).ok({timeout}) | ||||
|       .hover(getNthStatus(i)) | ||||
|       .expect($('.loading-footer').exist).notOk() | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue