Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 169-add-date-to-examtype
  • 233-make-exam-a-many-to-many-field-on-studentinfo-model
  • 236-improve-importer-experience
  • 243-replace-toggle-buttons-with-switches
  • 250-update-vuetify
  • 258-add-markdown-viewer
  • 265-fix-selection-changing-on-window-switching
  • 272-reviewers-should-be-able-to-assign-exercise-groups-to-tutors
  • 276-create-new-yarn-lockfile
  • 279-tutor-overview-no-scrolling
  • 282-copy-button-does-not-work-when-reviewing-corrections
  • 286-fix-misalignment-of-hide-show-sidebar-buttons
  • 287-build-test-image-constantly-failing
  • 288-add-dropdown-to-participantspage-to-set-students-groups
  • 289-fix-change-log-card
  • 291-revise-to-old-export-scheme
  • 292-update-gitlab-ci-config-for-new-runner
  • 292-update-gitlab-ci-config-for-new-runner-2
  • add-exercise-util-script
  • document-frontend-components
  • grady-exam
  • jakob.dieterle-master-patch-13835
  • master
  • parallel-test
  • test-233-branch-remove-examtype-foreign-key-on-group
  • update-export-dialogs
  • 0.0.1
  • 0.1
  • 0.2
  • 0.3
  • 0.4
  • 0.4.1
  • 0.4.2
  • 0.5.0
  • 0.5.1
  • 1.0.0
  • 1.1.0
  • 2.0.0
  • 2.0.1
  • 2.1.0
  • 2.1.1
  • 2.2.0
  • 3.0.0
  • 3.0.1
  • 4.0.0
  • 4.1.0
  • 4.2.0
  • 4.3.0
  • 4.4.0
  • 4.4.1
  • 5.0.0
  • 5.0.1
  • 5.1.0
  • 5.1.1
  • 5.1.2
  • 5.1.3
  • 5.1.4
  • 5.1.5
  • 5.1.6
  • 5.1.7
  • 5.2.0
  • 5.3.0
  • 5.3.1
  • 5.3.2
  • 5.4.0
  • 5.4.1
  • 5.4.2
  • 6.0.0
  • 6.1.0
  • legacy
70 results

Target

Select target project
  • j.michal/grady
1 result
Select Git revision
  • 169-add-date-to-examtype
  • 233-make-exam-a-many-to-many-field-on-studentinfo-model
  • 236-improve-importer-experience
  • 243-replace-toggle-buttons-with-switches
  • 250-update-vuetify
  • 258-add-markdown-viewer
  • 265-fix-selection-changing-on-window-switching
  • 272-reviewers-should-be-able-to-assign-exercise-groups-to-tutors
  • 276-create-new-yarn-lockfile
  • 279-tutor-overview-no-scrolling
  • 282-copy-button-does-not-work-when-reviewing-corrections
  • 286-fix-misalignment-of-hide-show-sidebar-buttons
  • 287-build-test-image-constantly-failing
  • 288-add-dropdown-to-participantspage-to-set-students-groups
  • 289-fix-change-log-card
  • 291-revise-to-old-export-scheme
  • 292-update-gitlab-ci-config-for-new-runner
  • 292-update-gitlab-ci-config-for-new-runner-2
  • add-exercise-util-script
  • document-frontend-components
  • grady-exam
  • jakob.dieterle-master-patch-13835
  • master
  • parallel-test
  • test-233-branch-remove-examtype-foreign-key-on-group
  • update-export-dialogs
  • 0.0.1
  • 0.1
  • 0.2
  • 0.3
  • 0.4
  • 0.4.1
  • 0.4.2
  • 0.5.0
  • 0.5.1
  • 1.0.0
  • 1.1.0
  • 2.0.0
  • 2.0.1
  • 2.1.0
  • 2.1.1
  • 2.2.0
  • 3.0.0
  • 3.0.1
  • 4.0.0
  • 4.1.0
  • 4.2.0
  • 4.3.0
  • 4.4.0
  • 4.4.1
  • 5.0.0
  • 5.0.1
  • 5.1.0
  • 5.1.1
  • 5.1.2
  • 5.1.3
  • 5.1.4
  • 5.1.5
  • 5.1.6
  • 5.1.7
  • 5.2.0
  • 5.3.0
  • 5.3.1
  • 5.3.2
  • 5.4.0
  • 5.4.1
  • 5.4.2
  • 6.0.0
  • 6.1.0
  • legacy
70 results
Show changes
Showing
with 2849 additions and 1140 deletions
import {AxiosError} from "axios" import { AxiosError } from 'axios'
import {Dispatch} from "vuex" import { Dispatch } from 'vuex'
import {MutationHandler} from "vuex-typex" import { MutationHandler } from 'vuex-typex'
export function nameSpacer (namespace: string) { export function nameSpacer (namespace: string) {
return function (commitType: string) { return function (commitType: string) {
...@@ -40,15 +40,15 @@ interface GetSetPair<P> { ...@@ -40,15 +40,15 @@ interface GetSetPair<P> {
* @returns {*} * @returns {*}
*/ */
export function createComputedGetterSetter<P> ( export function createComputedGetterSetter<P> (
{path, mutation, namespace}: { path, mutation, namespace }:
{path: string, mutation: string | ((payload?: P) => void), namespace:string}): GetSetPair<P> { {path: string, mutation: string | ((payload?: P) => void), namespace:string}): GetSetPair<P> {
return { return {
get (): any { get (): any {
return getObjectValueByPath((this as any).$store.state, path) return getObjectValueByPath((this as any).$store.state, path)
}, },
set (val: P): void { set (val: P): void {
if (typeof mutation === "string") { if (typeof mutation === 'string') {
(this as any).$store.commit(`${namespace ? namespace + '/' : ''}${mutation}`, val) (this as any).$store.commit(`${namespace ? namespace + '/' : ''}${mutation}`, val)
} else { } else {
mutation(val) mutation(val)
} }
...@@ -74,13 +74,13 @@ interface MappedState { ...@@ -74,13 +74,13 @@ interface MappedState {
* @param items array that contains objects {name, path, mutation} * @param items array that contains objects {name, path, mutation}
*/ */
export function mapStateToComputedGetterSetter ( export function mapStateToComputedGetterSetter (
{namespace = '', pathPrefix = '', items = []}: { namespace = '', pathPrefix = '', items = [] }:
{namespace: string, pathPrefix: string, items: StateMapperItem[]}): MappedState { {namespace: string, pathPrefix: string, items: StateMapperItem[]}): MappedState {
return items.reduce((acc: MappedState, curr) => { return items.reduce((acc: MappedState, curr) => {
// if no path is give, use name // if no path is give, use name
const itemPath = curr.path || curr.name const itemPath = curr.path || curr.name
const path = pathPrefix ? `${pathPrefix}.${itemPath}` : itemPath const path = pathPrefix ? `${pathPrefix}.${itemPath}` : itemPath
acc[curr.name] = createComputedGetterSetter({mutation: curr.mutation, path, namespace}) acc[curr.name] = createComputedGetterSetter({ mutation: curr.mutation, path, namespace })
return acc return acc
}, {}) }, {})
} }
...@@ -88,7 +88,7 @@ export function mapStateToComputedGetterSetter ( ...@@ -88,7 +88,7 @@ export function mapStateToComputedGetterSetter (
// thanks to rsp // thanks to rsp
// https://stackoverflow.com/questions/12303989/cartesian-product-of-multiple-arrays-in-javascript/43053803#43053803 // https://stackoverflow.com/questions/12303989/cartesian-product-of-multiple-arrays-in-javascript/43053803#43053803
function cartesianHelper (a: Array<any>, b: Array<any>): Array<Array<any>> { function cartesianHelper (a: Array<any>, b: Array<any>): Array<Array<any>> {
return ([] as Array<any>).concat(...a.map((a: any) => b.map((b: any) => [].concat(a, b)))) return ([] as Array<any>).concat(...a.map((a: any) => b.map((b: any) => [].concat(a, b))))
} }
export function cartesian (a: Array<any>, b?: Array<any>, ...c: Array<Array<any>>): Array<Array<any>> { export function cartesian (a: Array<any>, b?: Array<any>, ...c: Array<Array<any>>): Array<Array<any>> {
return b ? cartesian(cartesianHelper(a, b), ...c) : a return b ? cartesian(cartesianHelper(a, b), ...c) : a
...@@ -121,22 +121,6 @@ export function once (fn: Function, context?: object): OnceFunc { ...@@ -121,22 +121,6 @@ export function once (fn: Function, context?: object): OnceFunc {
} }
return result return result
} }
wrapped.reset = function () {result = undefined} wrapped.reset = function () { result = undefined }
return wrapped return wrapped
} }
export function handleError (err: Error, dispatch: Dispatch, fallbackMsg: string): any
export function handleError (err: AxiosError, dispatch: Dispatch, fallbackMsg: string): any
export function handleError (err: any, dispatch: Dispatch, fallbackMsg: string): any {
if (err.response) {
if (err.response.status === 401) {
dispatch('logout', 'You\'ve been logged out')
} else {
throw err
}
} else {
if (fallbackMsg) {
throw new Error(fallbackMsg)
}
}
}
module.exports = { module.exports = {
env: { env: {
mocha: true mocha: true
},
rules: {
'import/no-extraneous-dependencies': 'off'
} }
} }
import Vuex from 'vuex'
import { mount, createLocalVue } from '@vue/test-utils'
import AutoLogout from '@/components/AutoLogout.vue'
import sinon from 'sinon'
import { Authentication } from '@/store/modules/authentication'
import { SET_LAST_INTERACTION } from '@/store/mutations'
import { lastInteraction } from '@/store/plugins/lastInteractionPlugin'
import chai from 'chai'
chai.should()
let localVue = createLocalVue()
localVue.use(Vuex)
describe('Auto Logout Unit Tests', () => {
let store: any = null
let consoleTemp = {
warn: console.warn,
error: console.error
}
before(function () {
console.warn = function () {}
console.error = function () {}
})
after(function () {
console.warn = consoleTemp.warn
console.error = consoleTemp.error
})
beforeEach(() => {
store = new Vuex.Store({
plugins: [lastInteraction]
})
})
afterEach(() => {
sinon.restore()
})
it('should be hidden by default', () => {
let wrapper = mount(AutoLogout, { localVue: localVue, store })
wrapper.vm.$data.logoutDialog.should.equal(false)
wrapper.html().should.contain('<div class="v-dialog v-dialog--persistent" style="max-width: 30%; display: none;">')
})
it('should be visible when logoutDialog is set to true', () => {
let store = new Vuex.Store({})
let wrapper = mount(AutoLogout, { localVue: localVue, store })
wrapper.vm.$data.logoutDialog = true
wrapper.html().should.contain('<div class="v-dialog v-dialog--active v-dialog--persistent" style="max-width: 30%;">')
})
it('should get a refresh token from the server when user clicks button', () => {
let spy = sinon.spy()
sinon.replace(Authentication, 'refreshJWT', spy)
let store = new Vuex.Store({})
let wrapper = mount(AutoLogout, { localVue: localVue, store })
wrapper.find('#continue-btn').trigger('click')
spy.called.should.equal(true)
})
it('should automatically update jwt when it is older than 20% of its lifetime', () => {
// replace lastTokenRefreshTry and jwtTimeDelta from the store to emulate wanted behaviour
let wrapper = mount(AutoLogout, { localVue: localVue, store })
let spy = sinon.spy()
sinon.replaceGetter(Authentication.state, 'jwtTimeDelta', () => 10 * 1e3)
sinon.replaceGetter(Authentication.state, 'lastTokenRefreshTry', () => Date.now() - 2.1 * 1e3)
sinon.replace(Authentication, 'refreshJWT', spy)
})
})
import Vuex from 'vuex'
import { mount, createLocalVue } from '@vue/test-utils'
import DataExport from '@/components/export/DataExport.vue'
import sinon from 'sinon'
import chai from 'chai'
import VueRouter from 'vue-router'
import testUtils from '@/../tests/utils/testUtils'
import * as api from '@/api'
chai.should()
let localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(VueRouter)
let router = new VueRouter()
// @ts-ignore
global.requestAnimationFrame = cb => cb()
describe('DataExport Component Unit Tests', () => {
let store: any = null
let consoleTemp = {
warn: console.warn,
error: console.error
}
before(function () {
console.warn = function () {}
console.error = function () {}
})
after(function () {
console.warn = consoleTemp.warn
console.error = consoleTemp.error
})
beforeEach(() => {
store = new Vuex.Store({
state: {}
})
})
afterEach(() => {
sinon.restore()
})
it('should download CSV when selected', () => {
let wrapper = mount(DataExport, { localVue: localVue, store })
wrapper.vm.$data.exportType = 'CSV'
let spy = sinon.spy()
// @ts-ignore
sinon.replace(wrapper.vm, 'jsonToCSV', spy)
// @ts-ignore
sinon.replace(wrapper.vm, 'createDownloadPopup', sinon.spy())
// @ts-ignore
wrapper.vm.optionalConvertAndCreatePopup(testUtils.studentExports)
spy.called.should.equal(true)
})
it('should download JSON when selected', () => {
let wrapper = mount(DataExport, { localVue: localVue, store })
wrapper.vm.$data.exportType = 'JSON'
let spy = sinon.spy()
// @ts-ignore
sinon.replace(wrapper.vm, 'jsonToCSV', spy)
// @ts-ignore
sinon.replace(wrapper.vm, 'createDownloadPopup', sinon.spy())
// @ts-ignore
wrapper.vm.optionalConvertAndCreatePopup(testUtils.studentExports)
spy.called.should.equal(false)
})
it('should download obfuscated data when no mapping was selected', async () => {
let wrapper = mount(DataExport, { localVue: localVue, store })
let stub = sinon.stub().returns(Promise.resolve({ data: testUtils.studentExports }))
let spy = sinon.spy()
// @ts-ignore replace ax.post because of fetch in getExportFile
sinon.replace(api.default, 'post', stub)
// @ts-ignore
sinon.replace(wrapper.vm, 'optionalConvertAndCreatePopup', spy)
// @ts-ignore
await wrapper.vm.getExportFile('data')
spy.calledWithExactly(testUtils.studentExports).should.equal(true)
})
it('should download deobfuscated data when mapping was selected', async () => {
let wrapper = mount(DataExport, { localVue: localVue, store })
let stub = sinon.stub().returns(Promise.resolve({ data: testUtils.studentExports }))
let spy = sinon.spy()
// @ts-ignore
wrapper.vm.mapFile = testUtils.fakeFile
// @ts-ignore replace ax.post because of fetch in getExportFile
sinon.replace(api.default, 'post', stub)
// @ts-ignore
sinon.replace(wrapper.vm, 'getMappedExportFile', spy)
// @ts-ignore
await wrapper.vm.getExportFile('data')
spy.calledWithExactly(testUtils.studentExports).should.equal(true)
})
it('should correctly apply the student-map file', () => {
let wrapper = mount(DataExport, { localVue: localVue, store })
let mapStub = sinon.stub().returns(testUtils.studentMap)
let spy = sinon.spy()
// @ts-ignore
sinon.replaceGetter(wrapper.vm, 'studentMap', mapStub)
// @ts-ignore
sinon.replaceGetter(wrapper.vm, 'mapFileLoaded', sinon.stub().returns(true))
// @ts-ignore
sinon.replace(wrapper.vm, 'optionalConvertAndCreatePopup', spy)
// @ts-ignore
wrapper.vm.getMappedExportFile(testUtils.studentExportsObfuscated)
spy.firstCall.args[0][0].Matrikel.should.equal(testUtils.studentMap['username'].matrikelNo)
})
})
import Vuex from 'vuex'
import { mount, createLocalVue, createWrapper } from '@vue/test-utils'
import InstanceExport from '@/components/export/InstanceExport.vue'
import sinon from 'sinon'
import chai from 'chai'
import VueRouter from 'vue-router'
import testUtils from '@/../tests/utils/testUtils'
import * as api from '@/api'
chai.should()
let localVue = createLocalVue()
localVue.use(Vuex)
localVue.use(VueRouter)
let router = new VueRouter()
// @ts-ignore
global.requestAnimationFrame = cb => cb()
describe('InstanceExport Component Unit Tests', () => {
let store: any = null
let consoleTemp = {
warn: console.warn,
error: console.error
}
before(function () {
console.warn = function () {}
console.error = function () {}
})
after(function () {
console.warn = consoleTemp.warn
console.error = consoleTemp.error
})
beforeEach(() => {
store = new Vuex.Store({
state: {}
})
})
afterEach(() => {
sinon.restore()
})
it('should correctly apply the student-map file', () => {
let wrp = mount(InstanceExport, { localVue: localVue, store })
let mapStub = sinon.stub().returns(testUtils.studentMap)
let spy = sinon.spy()
// @ts-ignore
sinon.replaceGetter(wrp.vm, 'studentMap', mapStub)
// @ts-ignore
sinon.replaceGetter(wrp.vm, 'mapFileLoaded', sinon.stub().returns(true))
// @ts-ignore
sinon.replace(wrp.vm, 'optionalConvertAndCreatePopup', spy)
// @ts-ignore
wrp.vm.getMappedExportFile(testUtils.instanceExportsObfuscated)
spy.firstCall.args[0].students[0].matrikelNo.should.equal(testUtils.studentMap['username'].matrikelNo)
})
})
\ No newline at end of file
import { exportMixin, ExportType } from '@/components/mixins/mixins'
import sinon from 'sinon'
import ax from '@/api'
import testUtils, { FakeFileReader } from '@/../tests/utils/testUtils'
import chai from 'chai'
import { mutations as mut } from '@/store/mutations'
chai.should()
describe('Export Mixin Unit Tests', () => {
describe('getExportFile()', () => {
let e = new exportMixin()
afterEach(() => {
sinon.restore()
})
it('should fetch from the dataExport endpoint when called with "data"', async () => {
let stub = sinon.stub().resolves({ data: testUtils.studentExports })
sinon.replace(ax, 'post', stub)
sinon.replace(e, 'optionalConvertAndCreatePopup', () => {})
await e.getExportFile('data')
stub.calledWith('/api/export/json/').should.equal(true)
})
it('should fetch from instanceExport endpoint when called with "instance"', async () => {
let stub = sinon.stub().resolves({ data: testUtils.instanceExports })
sinon.replace(ax, 'get', stub)
sinon.replace(e, 'optionalConvertAndCreatePopup', () => {})
await e.getExportFile('instance')
stub.calledWith('/api/instance/export').should.equal(true)
})
it('should try to apply mapping when map file is given', async () => {
let spy = sinon.spy()
e.mapFile = testUtils.fakeFile
sinon.replace(ax, 'post', sinon.stub().resolves({ data: testUtils.studentExports }))
sinon.replace(e, 'getMappedExportFile', spy)
await e.getExportFile('data')
e.mapFile = null
spy.calledOnce.should.equal(true)
spy.calledWith(testUtils.studentExports).should.equal(true)
})
it('should call popup creation method with proper arguments when no mapping file is given', async () => {
let spy = sinon.spy()
sinon.replace(ax, 'post', sinon.stub().resolves({ data: testUtils.studentExports }))
sinon.replace(e, 'optionalConvertAndCreatePopup', spy)
await e.getExportFile('data')
spy.calledOnce.should.equal(true)
spy.calledWith(testUtils.studentExports).should.equal(true)
})
})
describe('optionalConvertAndCreatePopup()', () => {
let e = new exportMixin()
afterEach(() => {
sinon.restore()
})
it('should convert data to csv when csv is selected in dropdown', () => {
let stub = sinon.stub().returns('students as csv')
let spy = sinon.spy()
e.exportType = ExportType.CSV
sinon.replace(e, 'jsonToCSV', stub)
sinon.replace(e, 'createDownloadPopup', spy)
e.optionalConvertAndCreatePopup(testUtils.studentExports)
stub.calledWith(testUtils.studentExports).should.equal(true)
spy.calledWith('students as csv', ExportType.CSV).should.equal(true)
})
})
describe('readMapFileAndCommit', () => {
let e = new exportMixin()
// @ts-ignore
global.FileReader = FakeFileReader
afterEach(() => {
sinon.restore()
})
it('should throw an error when no mapfile was given', () => {
e.readMapFileAndCommit().catch((res) => {
res.should.not.equal(undefined)
})
})
it('should read selected mapFile', async () => {
e.mapFile = testUtils.fakeFile
let stub = sinon.stub()
// @ts-ignore
sinon.replace(global.FileReader.prototype, 'readAsText', stub)
e.readMapFileAndCommit()
e.mapFile = null
stub.args[0][0].name.should.equal(testUtils.fakeFile.name)
})
it('should parse file and trigger mutation when file was read', async () => {
e.mapFile = testUtils.fakeFile
let stub = sinon.stub().returns(testUtils.studentMap)
let mutSpy = sinon.spy()
sinon.replace(e, 'parseCSVMap', stub)
sinon.replace(mut, 'SET_STUDENT_MAP', mutSpy)
await e.readMapFileAndCommit()
e.mapFile = null
stub.calledOnce.should.equal(true)
stub.calledWithExactly('Grady testUtils fake result').should.equal(true)
mutSpy.calledWithExactly(testUtils.studentMap).should.equal(true)
})
})
describe('getMappedExportFile', () => {
let e = new exportMixin
afterEach(() => {
sinon.restore()
})
it('should read mapFile if it is not already loaded when used with instanceExports then apply mapping and create popup', async () => {
e.mapFile = testUtils.fakeFile
let spy = sinon.spy()
sinon.replace(e, 'readMapFileAndCommit', spy)
sinon.replace(e, 'applyMapping', () => {})
sinon.replace(e, 'optionalConvertAndCreatePopup', () => {})
await e.getMappedExportFile(testUtils.instanceExports)
e.mapFile = null
spy.called.should.equal(true)
})
it('should read mapFile if it is not already loaded when used with dataExport then apply mapping and create popup', async () => {
e.mapFile = testUtils.fakeFile
let spy = sinon.spy()
sinon.replace(e, 'readMapFileAndCommit', spy)
sinon.replace(e, 'applyMapping', () => {})
sinon.replace(e, 'optionalConvertAndCreatePopup', () => {})
await e.getMappedExportFile(testUtils.studentExports)
e.mapFile = null
spy.called.should.equal(true)
})
it('should apply the mapping and then open popup when mapFile is loaded', async () => {
sinon.replaceGetter(e, 'mapFileLoaded', () => { return true })
let mappingSpy = sinon.spy()
let popupSpy = sinon.spy()
sinon.replace(e, 'applyMapping', mappingSpy)
sinon.replace(e, 'optionalConvertAndCreatePopup', popupSpy)
await e.getMappedExportFile(testUtils.instanceExports)
e.mapFile = null
mappingSpy.calledWithExactly(testUtils.instanceExports).should.equal(true)
popupSpy.calledWithExactly(testUtils.instanceExports).should.equal(true)
})
})
describe('jsonToCSV()', () => {
let e = new exportMixin
it('should correctly parse JSON input to CSV with ; delimiter', () => {
let csv = e.jsonToCSV(testUtils.studentExports)
let expected = 'Matrikel;Name;Username;Sum;Exam;Password;test01\n' +
'1000000;name;username;100;exam;pwd;100\n'
csv.should.equal(expected)
})
})
})
import { StudentExportItem, InstanceExportData } from '@/api'
export class FakeFileReader {
result: String = 'Grady testUtils fake result'
constructor () {}
// readAsText will execute the callback that was provided to onload
readAsText (file: File) { this.onload({ target: this }) }
onload: Function = () => { }
}
export default {
studentExports: <StudentExportItem[]> [{
Matrikel: '1000000',
Name: 'name',
Username: 'username',
Sum: 100,
Exam: 'exam',
Password: 'pwd',
Scores: [{ type: 'test01', score: 100 }]
}],
studentExportsObfuscated: <StudentExportItem[]> [{
Matrikel: 'username',
Name: 'name',
Username: 'username',
Sum: 100,
Exam: 'exam',
Password: 'pwd',
Scores: [{ type: 'test01', score: 100 }]
}],
instanceExports: <InstanceExportData> {
students: [{
name: 'name',
matrikelNo: '1000000'
}]
},
instanceExportsObfuscated: <InstanceExportData> {
students: [{
name: 'name',
matrikelNo: 'username'
}]
},
fakeFile: <File> {
lastModified: 1,
name: 'Grady testUtils fake file',
size: 1,
slice: (start?: number | undefined, end?: number | undefined, contentType?: string | undefined): Blob => { return new Blob() },
type: 'fake file'
},
studentMap: {
'username': {
matrikelNo: '1000000', name: 'name'
}
},
}
...@@ -10,6 +10,11 @@ ...@@ -10,6 +10,11 @@
"esModuleInterop": true, "esModuleInterop": true,
"sourceMap": true, "sourceMap": true,
"baseUrl": ".", "baseUrl": ".",
"types": [
"webpack-env",
"mocha",
"chai"
],
"paths": { "paths": {
"@/*": [ "@/*": [
"src/*" "src/*"
...@@ -34,4 +39,4 @@ ...@@ -34,4 +39,4 @@
"exclude": [ "exclude": [
"node_modules" "node_modules"
] ]
} }
\ No newline at end of file
...@@ -17,7 +17,7 @@ module.exports = { ...@@ -17,7 +17,7 @@ module.exports = {
// keep_fnames ist set to true because vuex-typex is dependant on the function names // keep_fnames ist set to true because vuex-typex is dependant on the function names
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
config.optimization.minimizer[0].options.uglifyOptions.keep_fnames = true config.optimization.minimizer[0].options.terserOptions.keep_fnames = true
} }
} }
} }
This diff is collapsed.
import datetime
import time
import logging
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
from core.models import UserAccount
from functional_tests.util import (login, create_browser)
from util import factory_boys as fact
log = logging.getLogger(__name__)
class TestAutoLogout(LiveServerTestCase):
browser: webdriver.Firefox = None
username = None
password = None
role = None
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.browser = create_browser()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
cls.browser.quit()
def setUp(self):
self.username = 'reviewer'
self.password = 'p'
self.role = UserAccount.TUTOR
fact.UserAccountFactory(
username=self.username,
password=self.password,
role=self.role
)
def _login(self):
login(self.browser, self.live_server_url, self.username, self.password)
def test_auto_logout_can_continue(self):
with self.settings(JWT_AUTH={
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=5),
'JWT_ALLOW_REFRESH': True,
}
):
self._login()
initial_token = self.browser.execute_script(
'return sessionStorage["token"]'
)
logout_dialog = self.browser.find_element_by_id('logout-dialog')
WebDriverWait(self.browser, 5).until(
ec.visibility_of_element_located((By.ID, 'logout-dialog')))
logout_dialog.find_element_by_id('continue-btn').click()
# explicit sleep is unfortunately necessary here, since we need to wait
# for the initial token to expire and the site to be the same
time.sleep(2)
self.assertNotIn('login', self.browser.current_url)
new_token = self.browser.execute_script(
'return sessionStorage["token"]'
)
self.assertNotEqual(initial_token, new_token)
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
from core.models import UserAccount
from functional_tests.util import login, create_browser, reset_browser_after_test
from util import factory_boys as fact
class ExportTestModal(LiveServerTestCase):
browser: webdriver.Firefox = None
username = None
password = None
role = None
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.browser = create_browser()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
cls.browser.quit()
def setUp(self):
self.username = 'reviewer'
self.password = 'p'
self.role = UserAccount.REVIEWER
fact.UserAccountFactory(
username=self.username,
password=self.password,
role=self.role
)
def tearDown(self):
reset_browser_after_test(self.browser, self.live_server_url)
def _login(self):
login(self.browser, self.live_server_url, self.username, self.password)
def test_export_red_uncorrected_submissions(self):
def export_btn_is_red(*args):
exports_btn = self.browser.find_element_by_id('export-btn')
return 'red' in exports_btn.get_attribute('class')
fact.SubmissionFactory()
self._login()
WebDriverWait(self.browser, 3).until(export_btn_is_red)
def test_export_warning_tooltip_uncorrected_submissions(self):
fact.SubmissionFactory()
self._login()
tooltip_uncorrected = self.browser.find_element_by_id('uncorrected-tooltip')
self.assertNotEqual(None, tooltip_uncorrected)
self.assertRaises(Exception, self.browser.find_element_by_id, 'corrected-tooltip')
def test_export_green_all_corrected(self):
self._login()
exports_btn = self.browser.find_element_by_id('export-btn')
self.assertIn('green', exports_btn.get_attribute('class'))
def test_export_allgood_tooltip_all_corrected(self):
self._login()
tooltip_corrected = self.browser.find_element_by_id('corrected-tooltip')
self.assertNotEqual(None, tooltip_corrected)
self.assertRaises(Exception, self.browser.find_element_by_id, 'uncorrected-tooltip')
def test_export_list_popup_contains_correct_items(self):
self._login()
export_btn = self.browser.find_element_by_id('export-btn')
export_btn.click()
export_menu = self.browser.find_element_by_class_name('menuable__content__active')
export_list = export_menu.find_element_by_class_name('v-list')
list_elements = export_list.find_elements_by_tag_name('div')
self.assertEqual(2, len(list_elements))
self.assertEqual('Export student scores',
list_elements[0].find_element_by_tag_name('a').text)
self.assertEqual('Export whole instance data',
list_elements[1].find_element_by_tag_name('a').text)
def test_export_student_scores_as_json(self):
self._login()
fact.StudentInfoFactory()
export_btn = self.browser.find_element_by_id('export-btn')
export_btn.click()
export_scores = self.browser.find_element_by_id('export-list0')
export_scores.click()
data_export_modal = self.browser.find_element_by_id('data-export-modal')
export_type_select = data_export_modal.find_element_by_id('type-select')
export_type_select.click()
export_type_json = data_export_modal.find_element_by_xpath("//*[contains(text(), 'JSON')]")
export_type_json.click()
data_export_btn = data_export_modal.find_element_by_id('export-data-download-btn')
before_click_handles = self.browser.window_handles
data_export_btn.click()
WebDriverWait(self.browser, 10).until(ec.new_window_is_opened(before_click_handles))
tabs = self.browser.window_handles
self.assertEqual(2, len(tabs))
self.browser.switch_to.window(tabs[1])
self.assertIn('B.Inf.4242 Test Module', self.browser.find_element_by_tag_name('body').text)
def test_export_instance(self):
self._login()
fact.SubmissionFactory()
export_btn = self.browser.find_element_by_id('export-btn')
export_btn.click()
export_instance = self.browser.find_element_by_id('export-list1')
export_instance.click()
instance_export_modal = self.browser.find_element_by_id('instance-export-modal')
instance_export_btn = instance_export_modal.find_element_by_id('instance-export-dl')
before_click_handles = self.browser.window_handles
instance_export_btn.click()
WebDriverWait(self.browser, 10).until(ec.new_window_is_opened(before_click_handles))
after_click_handles = self.browser.window_handles
self.assertEqual(2, len(after_click_handles))
self.browser.switch_to.window(after_click_handles[1])
self.assertIn('B.Inf.4242 Test Module', self.browser.find_element_by_tag_name('body').text)
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.common.exceptions import StaleElementReferenceException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ec
from core.models import UserAccount, Submission, FeedbackComment
from functional_tests.util import (login, create_browser, reset_browser_after_test,
subscriptions_loaded_cond)
from util import factory_boys as fact
class UntestedParent:
class TestFeedbackCreationGeneric(LiveServerTestCase):
browser: webdriver.Firefox = None
username = None
password = None
role = None
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.browser = create_browser()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
cls.browser.quit()
def setUp(self):
self.sub_type = fact.SubmissionTypeFactory.create()
fact.SubmissionFactory.create_batch(2, type=self.sub_type)
def tearDown(self):
reset_browser_after_test(self.browser, self.live_server_url)
def _login(self):
login(self.browser, self.live_server_url, self.username, self.password)
def _reconstruct_submission_code(self):
sub_table = self.browser.find_element_by_class_name('submission-table')
lines = sub_table.find_elements_by_tag_name('tr')
line_no_code_pairs = [
(line.get_attribute('id'),
# call get_attribute here to get non normalized text
# https://github.com/SeleniumHQ/selenium/issues/2608
line.find_element_by_class_name('code-cell-content').get_attribute('textContent'))
for line in lines
]
line_no_code_pairs.sort(key=lambda x: x[0]) # sort by ids
code_lines = list(zip(*line_no_code_pairs))[1]
return '\n'.join(code_lines)
# stage can be 'initial', 'validate', or 'conflict'
def _go_to_subscription(self, stage='initial', sub_type=None):
tasks = self.browser.find_element_by_name('subscription-list')
WebDriverWait(self.browser, 3).until(subscriptions_loaded_cond(tasks))
tab = tasks.find_element_by_xpath(f'//*[contains(text(), "{stage}")]')
tab.click()
sub_type = sub_type if sub_type is not None else self.sub_type
sub_type_xpath = f'//*[contains(text(), "{sub_type.name}") ' \
f'and not(contains(@class, "inactive"))]'
WebDriverWait(self.browser, 3).until(
ec.element_to_be_clickable((By.XPATH, sub_type_xpath)),
message="SubmissionType not clickable"
)
sub_type_el = tasks.find_element_by_xpath(sub_type_xpath)
sub_type_el.click()
WebDriverWait(self.browser, 3).until(ec.url_contains('subscription'))
def write_comments_on_lines(self, line_comment_tuples):
""" line_comment_tuples is an iterable containing tuples of
(line_no, comment) where the line number starts at 1
"""
sub_table = self.browser.find_element_by_class_name('submission-table')
lines = sub_table.find_elements_by_tag_name('tr')
for (line_no, comment) in line_comment_tuples:
line = lines[line_no-1]
line.find_element_by_tag_name('button').click()
textarea = line.find_element_by_tag_name('textarea')
textarea.send_keys(comment)
line.find_element_by_id('submit-comment').click()
def test_student_text_is_correctly_displayed(self):
self._login()
self._go_to_subscription()
code = self._reconstruct_submission_code()
# query db for Submission with seen code, throws if not present and test fails
Submission.objects.get(text=code)
def test_submission_type_is_correctly_displayed(self):
self._login()
self._go_to_subscription()
sub_type_el = self.browser.find_element_by_id('submission-type')
title = sub_type_el.find_element_by_class_name('title')
self.assertEqual(
f'{self.sub_type.name} - Full score: {self.sub_type.full_score}',
title.text
)
solution = sub_type_el.find_element_by_class_name('solution-code')
self.assertEqual(self.sub_type.solution, solution.get_attribute('textContent'))
description = sub_type_el.find_element_by_class_name('type-description')
html_el_in_desc = description.find_element_by_tag_name('h1')
self.assertEqual('This', html_el_in_desc.text)
def test_test_output_is_displayed(self):
# create a test for every submission
test = None
for submission in Submission.objects.all():
test = fact.TestFactory.create(submission=submission, annotation='This is a test')
self._login()
self._go_to_subscription()
tests = self.browser.find_element_by_id('submission-tests')
name_label = tests.find_element_by_name('test-name-label')
name_label.click()
self.assertIn(test.name, name_label.text)
self.assertIn(test.label, name_label.text)
test_output = tests.find_element_by_name('test-output')
WebDriverWait(self.browser, 3).until(ec.visibility_of(test_output))
self.assertEqual(test.annotation, test_output.text)
def wait_until_code_changes(self, code):
def condition(*args):
try:
# code might change during the call resulting in the exception
new_code = self._reconstruct_submission_code()
except StaleElementReferenceException:
return False
return code != new_code
return condition
def test_can_give_max_score(self):
self._login()
self._go_to_subscription()
code = self._reconstruct_submission_code()
self.browser.find_element_by_id('score-full').click()
submit_btn = self.browser.find_element_by_id('submit-feedback')
submit_btn.click()
WebDriverWait(self.browser, 3).until(
self.wait_until_code_changes(code)
)
submission_for_code = Submission.objects.get(text=code)
self.assertEqual(self.sub_type.full_score, submission_for_code.feedback.score)
def test_zero_score_without_warning_gives_error(self):
self._login()
self._go_to_subscription()
self.browser.find_element_by_id('score-zero').click()
submit_btn = self.browser.find_element_by_id('submit-feedback')
submit_btn.click()
notification = self.browser.find_element_by_class_name('notification-content')
self.assertIn('comment', notification.text)
def test_can_give_zero_score(self):
self._login()
self._go_to_subscription()
code = self._reconstruct_submission_code()
self.browser.find_element_by_id('score-zero').click()
self.write_comments_on_lines([(0, 'A comment')])
self.browser.find_element_by_id('submit-feedback').click()
WebDriverWait(self.browser, 3).until(self.wait_until_code_changes(code))
submission_for_code = Submission.objects.get(text=code)
self.assertEqual(0, submission_for_code.feedback.score)
def test_can_give_comments_and_decreased_score(self):
self._login()
self._go_to_subscription()
code = self._reconstruct_submission_code()
# give half full score
score_input = self.browser.find_element_by_id('score-input')
score_input.send_keys(self.sub_type.full_score // 2)
# give feedback on first and last line of submission
comment_text = 'This is feedback'
self.write_comments_on_lines([
(1, comment_text), (0, comment_text) # 0 corresponds to the last line
])
submit_btn = self.browser.find_element_by_id('submit-feedback')
submit_btn.click()
WebDriverWait(self.browser, 3).until(
self.wait_until_code_changes(code)
)
submission_for_code = Submission.objects.get(text=code)
self.assertEqual(self.sub_type.full_score // 2, submission_for_code.feedback.score)
self.assertEqual(2, submission_for_code.feedback.feedback_lines.count())
fst_comment = FeedbackComment.objects.get(
of_feedback=submission_for_code.feedback,
of_line=1
)
self.assertEqual(comment_text, fst_comment.text)
last_line_of_sub = len(submission_for_code.text.split('\n'))
snd_comment = FeedbackComment.objects.get(
of_feedback=submission_for_code.feedback,
of_line=last_line_of_sub
)
self.assertEqual(comment_text, snd_comment.text)
def test_can_skip_submission(self):
self._login()
self._go_to_subscription()
code = self._reconstruct_submission_code()
self.browser.find_element_by_id('skip-submission').click()
WebDriverWait(self.browser, 3).until(self.wait_until_code_changes(code))
def test_can_validate_submission(self):
self._login()
self._go_to_subscription()
code = self._reconstruct_submission_code()
self.write_comments_on_lines([(0, 'A comment by me')])
self.browser.find_element_by_id('score-zero').click()
self.browser.find_element_by_id('submit-feedback').click()
WebDriverWait(self.browser, 3).until(self.wait_until_code_changes(code))
reset_browser_after_test(self.browser, self.live_server_url) # logs out user
user_snd = 'tutor_snd'
password = 'p'
fact.UserAccountFactory(username=user_snd, password=password)
login(self.browser, self.live_server_url, user_snd, password)
self._go_to_subscription(stage='validate')
self.write_comments_on_lines([(0, 'I disagree'), (1, 'Full points!')])
self.browser.find_element_by_id('score-full').click()
self.browser.find_element_by_id('submit-feedback').click()
WebDriverWait(self.browser, 5).until(ec.url_contains('subscription/ended'))
submission_for_code = Submission.objects.get(text=code)
self.assertEqual(self.sub_type.full_score, submission_for_code.feedback.score)
self.assertEqual(3, submission_for_code.feedback.feedback_lines.count())
class TestFeedbackCreationTutor(UntestedParent.TestFeedbackCreationGeneric):
def setUp(self):
super().setUp()
self.username = 'tutor'
self.password = 'p'
self.role = UserAccount.TUTOR
fact.UserAccountFactory(
username=self.username,
password=self.password
)
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from core import models
from core.models import UserAccount
from functional_tests.util import (login, create_browser, subscriptions_loaded_cond,
extract_hrefs_hashes, reset_browser_after_test)
from util import factory_boys as fact
# This is a little hack to have Super test class which implements common behaviour
# and tests but is not executed. In order to have the testrunner ignore the
# FrontPageTestsTutorReviewer class we need to define it within a class which does not inherit from
# unittest
class UntestedParent:
class FrontPageTestsTutorReviewer(LiveServerTestCase):
browser: webdriver.Firefox = None
username = None
password = None
role = None
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.browser = create_browser()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
cls.browser.quit()
def setUp(self):
fact.SubmissionFactory.create_batch(4)
def tearDown(self):
reset_browser_after_test(self.browser, self.live_server_url)
def _login(self):
login(self.browser, self.live_server_url, self.username, self.password)
def test_statistics_are_shown(self):
self._login()
statistics = self.browser.find_element_by_id('correction-statistics')
title = statistics.find_element_by_class_name('title')
self.assertEqual('Statistics', title.text)
def test_available_tasks_are_shown(self):
self._login()
tasks = self.browser.find_element_by_name('subscription-list')
title = tasks.find_element_by_class_name('v-toolbar__title')
self.assertEqual('Tasks', title.text)
WebDriverWait(self.browser, 6).until(subscriptions_loaded_cond(tasks))
subscription_links = extract_hrefs_hashes(
tasks.find_elements_by_tag_name('a')
)
subscriptions = models.SubmissionSubscription.objects.filter(
owner__username=self.username,
deactivated=False,
feedback_stage=models.SubmissionSubscription.FEEDBACK_CREATION
).exclude(query_type=models.SubmissionSubscription.RANDOM)
for sub in subscriptions:
self.assertIn(f'/subscription/{sub.pk}', subscription_links)
class FrontPageTestsTutor(UntestedParent.FrontPageTestsTutorReviewer):
def setUp(self):
super().setUp()
self.username = 'tutor'
self.password = 'p'
self.role = UserAccount.TUTOR
fact.UserAccountFactory(
username=self.username,
password=self.password
)
def test_side_bar_contains_correct_items(self):
self._login()
drawer = self.browser.find_element_by_class_name('v-navigation-drawer')
links = extract_hrefs_hashes(drawer.find_elements_by_tag_name('a'))
print(links)
self.assertTrue(all(link in links for link in ['/home', '/feedback']))
task_title = drawer.find_element_by_class_name('v-toolbar__title')
self.assertEqual('Tasks', task_title.text)
footer = drawer.find_element_by_class_name('sidebar-footer')
feedback_link = footer.find_element_by_css_selector('a.feedback-link')
self.assertEqual('Give us Feedback!', feedback_link.text)
self.assertEqual('https://gitlab.gwdg.de/j.michal/grady/issues',
feedback_link.get_attribute('href'))
class FrontPageTestsReviewer(UntestedParent.FrontPageTestsTutorReviewer):
def setUp(self):
super().setUp()
self.username = 'reviewer'
self.password = 'p'
self.role = UserAccount.REVIEWER
fact.UserAccountFactory(
username=self.username,
password=self.password,
role=self.role
)
def test_side_bar_contains_correct_items(self):
self._login()
drawer = self.browser.find_element_by_class_name('v-navigation-drawer')
links = extract_hrefs_hashes(drawer.find_elements_by_tag_name('a'))
self.assertTrue(all(link in links for link in
['/home', '/feedback', '/student-overview', '/tutor-overview']))
task_title = drawer.find_element_by_class_name('v-toolbar__title')
self.assertEqual('Tasks', task_title.text)
footer = drawer.find_element_by_class_name('sidebar-footer')
feedback_link = footer.find_element_by_css_selector('a.feedback-link')
self.assertEqual('Give us Feedback!', feedback_link.text)
self.assertEqual('https://gitlab.gwdg.de/j.michal/grady/issues',
feedback_link.get_attribute('href'))
import os
import time
from django.test import LiveServerTestCase from django.test import LiveServerTestCase
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.keys import Keys
from selenium.webdriver.firefox.options import Options from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.ui import WebDriverWait
from core.models import UserAccount from core.models import UserAccount
from functional_tests.util import get_frontend_url
from util.factories import make_test_data from util.factories import make_test_data
from functional_tests.util import create_browser, reset_browser_after_test
LiveServerTestCase.port = int(os.environ.get('LIVE_SERVER_PORT', 0)) class LoginPageTest(LiveServerTestCase):
browser: webdriver.Firefox = None
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.browser = create_browser()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
cls.browser.quit()
class LoginPageTest(LiveServerTestCase):
def setUp(self): def setUp(self):
self.live_server_url = get_frontend_url(self.live_server_url)
options = Options()
# funnily the method is marked deprecated but the alternative setter is not working...
options.headless = bool(os.environ.get('HEADLESS_TESTS', False))
self.browser = webdriver.Firefox(options=options)
self.browser.implicitly_wait(5)
self.test_data = make_test_data(data_dict={ self.test_data = make_test_data(data_dict={
'submission_types': [ 'submission_types': [
{ {
...@@ -92,7 +93,7 @@ class LoginPageTest(LiveServerTestCase): ...@@ -92,7 +93,7 @@ class LoginPageTest(LiveServerTestCase):
) )
def tearDown(self): def tearDown(self):
self.browser.quit() reset_browser_after_test(self.browser, self.live_server_url)
def _login(self, account): def _login(self, account):
self.browser.get(self.live_server_url) self.browser.get(self.live_server_url)
...@@ -101,7 +102,7 @@ class LoginPageTest(LiveServerTestCase): ...@@ -101,7 +102,7 @@ class LoginPageTest(LiveServerTestCase):
password_input = self.browser.find_element_by_xpath('//input[@aria-label="Password"]') password_input = self.browser.find_element_by_xpath('//input[@aria-label="Password"]')
password_input.send_keys('p') password_input.send_keys('p')
self.browser.find_element_by_xpath('//button[@type="submit"]').send_keys(Keys.ENTER) self.browser.find_element_by_xpath('//button[@type="submit"]').send_keys(Keys.ENTER)
time.sleep(1) WebDriverWait(self.browser, 3).until(ec.url_contains('/home'))
def test_tutor_can_login(self): def test_tutor_can_login(self):
tutor = self.test_data['tutors'][0] tutor = self.test_data['tutors'][0]
...@@ -129,9 +130,9 @@ class LoginPageTest(LiveServerTestCase): ...@@ -129,9 +130,9 @@ class LoginPageTest(LiveServerTestCase):
username_input.send_keys(username) username_input.send_keys(username)
password_input = self.browser.find_element_by_id('input-register-password') password_input = self.browser.find_element_by_id('input-register-password')
password_input.send_keys(password) password_input.send_keys(password)
self.browser.find_element_by_id('register-submit').click() register_submit_el = self.browser.find_element_by_id('register-submit')
time.sleep(1) register_submit_el.click()
WebDriverWait(self.browser, 3).until_not(ec.visibility_of(register_submit_el))
tutor = UserAccount.objects.get(username=username) tutor = UserAccount.objects.get(username=username)
self.assertEqual(UserAccount.TUTOR, tutor.role) self.assertEqual(UserAccount.TUTOR, tutor.role)
self.assertFalse(tutor.is_active, "Tutors should be inactive after registered") self.assertFalse(tutor.is_active, "Tutors should be inactive after registered")
import os import os
from itertools import islice
from typing import Sequence
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.ui import WebDriverWait
def get_frontend_url(live_server_url):
return os.environ.get('FRONTEND_URL', live_server_url) def create_browser() -> webdriver.Firefox:
options = Options()
options.headless = bool(os.environ.get('HEADLESS_TESTS', False))
browser = webdriver.Firefox(options=options)
browser.implicitly_wait(10)
browser.set_window_size(1920, 1080)
return browser
def login(browser, live_server_url, username, password='p'):
browser.get(live_server_url)
username_input = browser.find_element_by_xpath('//input[@aria-label="Username"]')
username_input.send_keys(username)
password_input = browser.find_element_by_xpath('//input[@aria-label="Password"]')
password_input.send_keys(password)
browser.find_element_by_xpath('//button[@type="submit"]').send_keys(Keys.ENTER)
WebDriverWait(browser, 3).until(ec.url_contains('/home'))
def reset_browser_after_test(browser: webdriver.Firefox, live_server_url):
while len(browser.window_handles) > 1:
browser.close()
browser.switch_to.window(browser.window_handles[0])
browser.execute_script("window.sessionStorage.clear()")
browser.get(live_server_url)
def nth(iterable, n, default=None):
"Returns the nth item or a default value"
return next(islice(iterable, n, None), default)
def extract_hrefs_hashes(web_elements: Sequence[WebElement]):
return [nth(el.get_attribute('href').split('#'), 1, '')
for el in web_elements if el.get_attribute('href')]
# A function that takes the element corresponding to the tasks
# component and return a function that can be used as a condition for
# WebDriverWait
def subscriptions_loaded_cond(tasks_el):
def loaded(*args):
sub_links = tasks_el.find_elements_by_tag_name('a')
return any((link != '/home' for link in extract_hrefs_hashes(sub_links)))
return loaded
...@@ -92,6 +92,7 @@ DATABASES = { ...@@ -92,6 +92,7 @@ DATABASES = {
'PASSWORD': os.environ.get('DB_PASSWORD', 'postgres'), 'PASSWORD': os.environ.get('DB_PASSWORD', 'postgres'),
'HOST': os.environ.get('DB_HOST', 'localhost'), 'HOST': os.environ.get('DB_HOST', 'localhost'),
'PORT': os.environ.get('DB_PORT', '5432'), 'PORT': os.environ.get('DB_PORT', '5432'),
'ATOMIC_REQUESTS': True
}, },
} }
......
from .default import * from .default import *
from .instance import *
from .live import * from .live import *
REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon'] = '1000/minute' REST_FRAMEWORK['DEFAULT_THROTTLE_RATES']['anon'] = '1000/minute'
flake8~=3.5.0 flake8~=3.6.0
pre-commit~=1.4.1 pre-commit~=1.13.0
pytest-cov~=2.5.1 pytest-cov~=2.6.0
pytest-django~=3.1.2 pytest-django~=3.4.0
selenium~=3.14.1 selenium~=3.141.0
factory-boy~=2.11.0
Faker~=1.0.0
django-cors-headers~=2.1.0 django-cors-headers~=2.4.0
django-extensions~=2.1 django-extensions~=2.1
djangorestframework-jwt~=1.11.0 djangorestframework-jwt~=1.11.0
djangorestframework~=3.8 djangorestframework~=3.8
git+https://github.com/robinhundt/djangorestframework-camel-case git+https://github.com/robinhundt/djangorestframework-camel-case
Django~=2.1 Django~=2.1
drf-yasg drf-yasg~=1.12.0
gevent~=1.3.2 gevent~=1.3.0
gunicorn~=19.7.0 gunicorn~=19.9.0
psycopg2-binary~=2.7.4 psycopg2-binary~=2.7.0
python-json-logger~=0.1.9 python-json-logger~=0.1.0
tqdm~=4.19.5 tqdm~=4.28.0
whitenoise~=3.3.1 whitenoise~=4.1.0
xlrd~=1.0.0 xlrd~=1.2.0
xkcdpass==1.16.5 xkcdpass==1.17.0