forked from cybrespace/pinafore
		
	start migrating to testcafe
This commit is contained in:
		
							parent
							
								
									b0a8ce1efb
								
							
						
					
					
						commit
						13a2195035
					
				
					 7 changed files with 1859 additions and 0 deletions
				
			
		
							
								
								
									
										1624
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1624
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
				
			
			@ -60,6 +60,7 @@
 | 
			
		|||
    "svelte-loader": "^2.3.3",
 | 
			
		||||
    "svelte-transitions": "^1.1.1",
 | 
			
		||||
    "svgo": "^1.0.3",
 | 
			
		||||
    "testcafe": "^0.19.0-alpha1",
 | 
			
		||||
    "timeago.js": "^3.0.2",
 | 
			
		||||
    "tiny-queue": "^0.2.1",
 | 
			
		||||
    "uglifyjs-webpack-plugin": "^1.1.5",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										54
									
								
								tests/fixtures.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								tests/fixtures.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
import times from 'lodash/times'
 | 
			
		||||
 | 
			
		||||
export const homeTimeline = [
 | 
			
		||||
  {content: 'pinned toot 1'},
 | 
			
		||||
  {content: 'notification of unlisted message'},
 | 
			
		||||
  {content: 'notification of followers-only message'},
 | 
			
		||||
  {content: 'notification of direct message'},
 | 
			
		||||
  {content: 'this is unlisted'},
 | 
			
		||||
  {content: 'this is followers-only'},
 | 
			
		||||
  {content: 'direct'},
 | 
			
		||||
  {spoiler: 'kitten CW'},
 | 
			
		||||
  {content: 'secret video'},
 | 
			
		||||
  {content: "here's a video"},
 | 
			
		||||
  {spoiler: 'CW'},
 | 
			
		||||
  {content: "here's a secret animated kitten gif"},
 | 
			
		||||
  {content: "here's an animated kitten gif"},
 | 
			
		||||
  {content: "here's 2 kitten photos"},
 | 
			
		||||
  {content: "here's a secret kitten"},
 | 
			
		||||
  {content: "here's a kitten"},
 | 
			
		||||
  {content: 'hello admin'},
 | 
			
		||||
  {content: 'hello foobar'},
 | 
			
		||||
  {content: 'hello world'}
 | 
			
		||||
].concat(times(30, i => ({content: (30 - i).toString()})))
 | 
			
		||||
 | 
			
		||||
export const localTimeline = [
 | 
			
		||||
  {spoiler: 'kitten CW'},
 | 
			
		||||
  {content: 'secret video'},
 | 
			
		||||
  {content: "here's a video"},
 | 
			
		||||
  {spoiler: 'CW'},
 | 
			
		||||
  {content: "here's a secret animated kitten gif"},
 | 
			
		||||
  {content: "here's an animated kitten gif"},
 | 
			
		||||
  {content: "here's 2 kitten photos"},
 | 
			
		||||
  {content: "here's a secret kitten"},
 | 
			
		||||
  {content: "here's a kitten"},
 | 
			
		||||
  {content: 'hello world'}
 | 
			
		||||
].concat(times(30, i => ({content: (30 - i).toString()})))
 | 
			
		||||
 | 
			
		||||
export const notifications = [
 | 
			
		||||
  {favoritedBy: 'admin'},
 | 
			
		||||
  {rebloggedBy: 'admin'},
 | 
			
		||||
  {content: 'notification of unlisted message'},
 | 
			
		||||
  {content: 'notification of followers-only message'},
 | 
			
		||||
  {content: 'notification of direct message'},
 | 
			
		||||
  {followedBy: 'quux'},
 | 
			
		||||
  {content: 'hello foobar'},
 | 
			
		||||
  {followedBy: 'admin'}
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
export const favorites = [
 | 
			
		||||
  {content: 'notification of direct message'},
 | 
			
		||||
  {content: 'notification of followers-only message'},
 | 
			
		||||
  {content: 'notification of unlisted message'},
 | 
			
		||||
  {content: 'pinned toot 1'}
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										33
									
								
								tests/spec/01-basic-spec.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								tests/spec/01-basic-spec.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
import { Selector as $ } from 'testcafe'
 | 
			
		||||
import { getUrl, settingsButton } from '../utils'
 | 
			
		||||
 | 
			
		||||
fixture `01-basic-spec.js`
 | 
			
		||||
  .page `http://localhost:4002`
 | 
			
		||||
 | 
			
		||||
test('has the correct <h1>', async t => {
 | 
			
		||||
  await t
 | 
			
		||||
    .expect($('.container h1').innerText).eql('Pinafore')
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('navigates to about', async t => {
 | 
			
		||||
  await t
 | 
			
		||||
    .click(settingsButton)
 | 
			
		||||
    .expect(getUrl()).contains('/settings')
 | 
			
		||||
    .click('a[href="/settings/about"]')
 | 
			
		||||
    .expect(getUrl()).contains('/about')
 | 
			
		||||
    .expect($('.container h1').innerText).eql('About Pinafore')
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('navigates to /settings/instances/add', async t => {
 | 
			
		||||
  await t.click($('a').withText('log in to an instance'))
 | 
			
		||||
    .expect(getUrl()).contains('/settings/instances/add')
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('navigates to settings/instances', async t => {
 | 
			
		||||
  await t.click(settingsButton)
 | 
			
		||||
    .expect(getUrl()).contains('/settings')
 | 
			
		||||
    .click($('a').withText('Instances'))
 | 
			
		||||
    .expect(getUrl()).contains('/settings/instances')
 | 
			
		||||
    .expect($('.container').innerText)
 | 
			
		||||
      .contains("You're not logged in to any instances")
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										37
									
								
								tests/spec/02-login-spec.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								tests/spec/02-login-spec.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
import { Selector as $ } from 'testcafe'
 | 
			
		||||
import { addInstanceButton, getUrl, instanceInput, login, settingsButton } from '../utils'
 | 
			
		||||
 | 
			
		||||
fixture `02-login-spec.js`
 | 
			
		||||
  .page `http://localhost:4002`
 | 
			
		||||
 | 
			
		||||
const formError = $('.form-error')
 | 
			
		||||
test('Cannot log in to a fake instance', async t => {
 | 
			
		||||
 | 
			
		||||
  await t.click($('a').withText('log in to an instance'))
 | 
			
		||||
    .typeText(instanceInput, 'fake.nolanlawson.com')
 | 
			
		||||
    .click(addInstanceButton)
 | 
			
		||||
    .expect(formError.exists).ok()
 | 
			
		||||
    .expect(formError.innerText).contains('Is this a valid Mastodon instance?')
 | 
			
		||||
    .typeText(instanceInput, '.biz')
 | 
			
		||||
    .expect(formError.exists).notOk()
 | 
			
		||||
    .typeText(instanceInput, 'fake.nolanlawson.com', {replace: true})
 | 
			
		||||
    .expect(formError.exists).ok()
 | 
			
		||||
    .expect(formError.innerText).contains('Is this a valid Mastodon instance?')
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Logs in to localhost:3000', async t => {
 | 
			
		||||
  await login(t, 'foobar@localhost:3000', 'foobarfoobar')
 | 
			
		||||
    .expect($('article.status-article').exists).ok()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Logs out', async t => {
 | 
			
		||||
  await login(t, 'foobar@localhost:3000', 'foobarfoobar')
 | 
			
		||||
    .click(settingsButton)
 | 
			
		||||
    .click($('a').withText('Instances'))
 | 
			
		||||
    .click($('a').withText('localhost:3000'))
 | 
			
		||||
    .expect(getUrl()).contains('/settings/instances/localhost:3000')
 | 
			
		||||
    .click($('button').withText('Log out'))
 | 
			
		||||
    .click($('#modal-dialog button').withText('OK'))
 | 
			
		||||
    .expect($('.container').innerText)
 | 
			
		||||
    .contains("You're not logged in to any instances")
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										53
									
								
								tests/spec/03-basic-timeline-spec.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								tests/spec/03-basic-timeline-spec.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
import { Selector as $ } from 'testcafe'
 | 
			
		||||
import { getUrl, login, validateTimeline } from '../utils'
 | 
			
		||||
import { homeTimeline, notifications, localTimeline, favorites } from '../fixtures'
 | 
			
		||||
 | 
			
		||||
fixture `03-basic-timeline-spec.js`
 | 
			
		||||
  .page `http://localhost:4002`
 | 
			
		||||
  .beforeEach(async t => {
 | 
			
		||||
    await login(t, 'foobar@localhost:3000', 'foobarfoobar')
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
const firstArticle = $('.virtual-list-item[aria-hidden=false] .status-article')
 | 
			
		||||
 | 
			
		||||
test('Shows the home timeline', async t => {
 | 
			
		||||
  await t
 | 
			
		||||
    .expect(firstArticle.hasAttribute('aria-setsize')).ok()
 | 
			
		||||
    .expect(firstArticle.getAttribute('aria-posinset')).eql('0')
 | 
			
		||||
 | 
			
		||||
  await validateTimeline(t, homeTimeline)
 | 
			
		||||
 | 
			
		||||
  await t.expect(firstArticle.getAttribute('aria-setsize')).eql('49')
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Shows notifications', async t => {
 | 
			
		||||
  await t.click($('nav a[aria-label=Notifications]'))
 | 
			
		||||
    .expect(getUrl()).contains('/notifications')
 | 
			
		||||
 | 
			
		||||
  await validateTimeline(t, notifications)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Shows the local timeline', async t => {
 | 
			
		||||
  await t.click($('nav a[aria-label=Local]'))
 | 
			
		||||
    await t.expect(getUrl()).contains('/local')
 | 
			
		||||
 | 
			
		||||
  await validateTimeline(t, localTimeline)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Shows the federated timeline', async t => {
 | 
			
		||||
  await t.click($('nav a[aria-label=Community]'))
 | 
			
		||||
    .expect(getUrl()).contains('/community')
 | 
			
		||||
    .click($('a').withText('Federated'))
 | 
			
		||||
    .expect(getUrl()).contains('/federated')
 | 
			
		||||
 | 
			
		||||
  await validateTimeline(t, localTimeline) // local is same as federated in this case
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Shows favorites', async t => {
 | 
			
		||||
  await t.click($('nav a[aria-label=Community]'))
 | 
			
		||||
    .expect(getUrl()).contains('/community')
 | 
			
		||||
    .click($('a').withText('Favorites'))
 | 
			
		||||
    .expect(getUrl()).contains('/favorites')
 | 
			
		||||
 | 
			
		||||
  await validateTimeline(t, favorites)
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										57
									
								
								tests/utils.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								tests/utils.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,57 @@
 | 
			
		|||
import { ClientFunction as exec, Selector as $ } from 'testcafe'
 | 
			
		||||
 | 
			
		||||
export const settingsButton = $('nav a[aria-label=Settings]')
 | 
			
		||||
export const instanceInput = $('#instanceInput')
 | 
			
		||||
export const addInstanceButton = $('.add-new-instance button')
 | 
			
		||||
 | 
			
		||||
export const getUrl = exec(() => window.location.href)
 | 
			
		||||
 | 
			
		||||
export function login(t, username, password) {
 | 
			
		||||
  return t.click($('a').withText('log in to an instance'))
 | 
			
		||||
    .expect(getUrl()).contains('/settings/instances/add')
 | 
			
		||||
    .typeText(instanceInput, 'localhost:3000')
 | 
			
		||||
    .click(addInstanceButton)
 | 
			
		||||
    .expect(getUrl()).eql('http://localhost:3000/auth/sign_in')
 | 
			
		||||
    .typeText($('input#user_email'), username)
 | 
			
		||||
    .typeText($('input#user_password'), password)
 | 
			
		||||
    .click($('button[type=submit]'))
 | 
			
		||||
    .expect(getUrl()).contains('/oauth/authorize')
 | 
			
		||||
    .click($('button[type=submit]:not(.negative)'))
 | 
			
		||||
    .expect(getUrl()).eql('http://localhost:4002/')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getNthVirtualArticle (n) {
 | 
			
		||||
  return $(`.virtual-list-item[aria-hidden="false"] article[aria-posinset="${n}"]`)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function validateTimeline(t, timeline) {
 | 
			
		||||
  for (let i = 0; i < timeline.length; i++) {
 | 
			
		||||
    let status = timeline[i]
 | 
			
		||||
    if (status.content) {
 | 
			
		||||
      await t.expect(getNthVirtualArticle(i).find('.status-content p').innerText)
 | 
			
		||||
        .contains(status.content)
 | 
			
		||||
    }
 | 
			
		||||
    if (status.spoiler) {
 | 
			
		||||
      await t.expect(getNthVirtualArticle(i).find('.status-spoiler p').innerText)
 | 
			
		||||
        .contains(status.spoiler)
 | 
			
		||||
    }
 | 
			
		||||
    if (status.followedBy) {
 | 
			
		||||
      await t.expect(getNthVirtualArticle(i).find('.status-header span').innerText)
 | 
			
		||||
        .contains(status.followedBy + ' followed you')
 | 
			
		||||
    }
 | 
			
		||||
    if (status.rebloggedBy) {
 | 
			
		||||
      await t.expect(getNthVirtualArticle(i).find('.status-header span').innerText)
 | 
			
		||||
        .contains(status.rebloggedBy + ' boosted your status')
 | 
			
		||||
    }
 | 
			
		||||
    if (status.favoritedBy) {
 | 
			
		||||
      await t.expect(getNthVirtualArticle(i).find('.status-header span').innerText)
 | 
			
		||||
        .contains(status.favoritedBy + ' favorited your status')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // hovering forces TestCafé to scroll to that element: https://git.io/vABV2
 | 
			
		||||
    if (i % 3 === 2) { // only scroll every nth element
 | 
			
		||||
      await t.hover(getNthVirtualArticle(i))
 | 
			
		||||
      await t.expect($('.loading-footer').exist).notOk()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		
		Reference in a new issue