diff --git a/src/main/java/org/olat/course/assessment/manager/AssessmentNotificationsHandler.java b/src/main/java/org/olat/course/assessment/manager/AssessmentNotificationsHandler.java index e04d332af362cb77057e23bf7cc307cd8a87ecab..f75e796ef3223d880b40bf61c9d01742a8ab58ef 100644 --- a/src/main/java/org/olat/course/assessment/manager/AssessmentNotificationsHandler.java +++ b/src/main/java/org/olat/course/assessment/manager/AssessmentNotificationsHandler.java @@ -124,14 +124,11 @@ public class AssessmentNotificationsHandler implements NotificationsHandler { public SubscriptionContext getAssessmentSubscriptionContext(Identity ident, ICourse course) { SubscriptionContext sctx = null; if (ident == null || canSubscribeForAssessmentNotification(ident, course)) { - // Creates a new SubscriptionContext only if not found into cache - if (sctx == null) { - // a subscription context showing to the root node (the course's root - // node is started when clicking such a notification) - CourseNode cn = course.getRunStructure().getRootNode(); - Long resourceableId = course.getResourceableId(); - sctx = new SubscriptionContext(CourseModule.ORES_COURSE_ASSESSMENT, resourceableId, cn.getIdent()); - } + // a subscription context showing to the root node (the course's root + // node is started when clicking such a notification) + CourseNode cn = course.getRunStructure().getRootNode(); + Long resourceableId = course.getResourceableId(); + sctx = new SubscriptionContext(CourseModule.ORES_COURSE_ASSESSMENT, resourceableId, cn.getIdent()); } return sctx; } @@ -310,14 +307,13 @@ public class AssessmentNotificationsHandler implements NotificationsHandler { // exceptions, course // can't be loaded when already deleted if (notificationsManager.isPublisherValid(p) && compareDate.before(latestNews)) { - Long courseId = new Long(p.getData()); + Long courseId = Long.valueOf(p.getData()); final ICourse course = loadCourseFromId(courseId); if (courseStatus(course)) { // course admins or users with the course right to have full access to // the assessment tool will have full access to user tests CourseGroupManager cgm = course.getCourseEnvironment().getCourseGroupManager(); - final boolean hasFullAccess = (cgm.isIdentityCourseAdministrator(identity) ? true : cgm.hasRight(identity, - CourseRights.RIGHT_ASSESSMENT)); + final boolean hasFullAccess = cgm.isIdentityCourseAdministrator(identity) || cgm.hasRight(identity, CourseRights.RIGHT_ASSESSMENT); final Set<Identity> coachedUsers = new HashSet<>(); if (!hasFullAccess) { // initialize list of users, only when user has not full access diff --git a/src/main/java/org/olat/modules/scorm/OLATApiAdapter.java b/src/main/java/org/olat/modules/scorm/OLATApiAdapter.java index 3b2502cf0b3d9304288fa174b305106da97825be..3f27abb8415c47715beca49af360fd8facc65e8d 100644 --- a/src/main/java/org/olat/modules/scorm/OLATApiAdapter.java +++ b/src/main/java/org/olat/modules/scorm/OLATApiAdapter.java @@ -44,7 +44,6 @@ import java.util.Set; import org.olat.core.logging.LogDelegator; import org.olat.core.logging.OLATRuntimeException; -import org.olat.core.util.FileUtils; import org.olat.core.util.StringHelper; import org.olat.modules.scorm.manager.ScormManager; import org.olat.modules.scorm.server.beans.LMSDataFormBean; @@ -63,10 +62,9 @@ import ch.ethz.pfplms.scorm.api.ApiAdapter; * @author guido */ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm.api.ApiAdapterInterface { - private final ApiAdapter core; - //private ScormTrackingManager scormTracking; - - private Hashtable<String,String> olatScoCmi = new Hashtable<String,String>(); + private final ApiAdapter core; + + private Hashtable<String,String> olatScoCmi = new Hashtable<>(); private String olatStudentId; private String olatStudentName; @@ -81,7 +79,7 @@ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm private LMSDataHandler odatahandler; private ScormManager scormManager; private SettingsHandlerImpl scormSettingsHandler; - private final List<ScormAPICallback> apiCallbacks = new ArrayList<ScormAPICallback>(2); + private final List<ScormAPICallback> apiCallbacks = new ArrayList<>(2); // private Properties scoresProp; // keys: sahsId; values = raw score of an sco private Properties lessonStatusProp; @@ -126,31 +124,21 @@ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm scorePropsFile = new File(savePath, "_olat_score.properties"); scoresProp = new Properties(); if (scorePropsFile.exists()) { - InputStream is = null; - try { - is = new BufferedInputStream(new FileInputStream(scorePropsFile)); + try(InputStream is = new BufferedInputStream(new FileInputStream(scorePropsFile))) { scoresProp.load(is); } catch (IOException e) { throw e; } - finally { - if (is != null) FileUtils.closeSafely(is); - } } lessonStatusPropsFile = new File(savePath, "_olat_lesson_status.properties"); lessonStatusProp = new Properties(); if (lessonStatusPropsFile.exists()) { - InputStream is = null; - try { - is = new BufferedInputStream(new FileInputStream(lessonStatusPropsFile)); + try(InputStream is = new BufferedInputStream(new FileInputStream(lessonStatusPropsFile))) { lessonStatusProp.load(is); } catch (IOException e) { throw e; } - finally { - if (is != null) FileUtils.closeSafely(is); - } } scormManager = new ScormManager(cpRoot.getAbsolutePath(), true, true, true, scormSettingsHandler); @@ -274,12 +262,10 @@ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm LMSDataFormBean lmsDataBean = new LMSDataFormBean(); lmsDataBean.setItemID(olatScoId); - //TODO:gs pass the dataBean for use, and do not get it a second time lmsDataBean.setNextAction("5"); lmsDataBean.setLmsAction("update"); - Map <String,String>cmiData = new HashMap<String,String>(); + Map <String,String>cmiData = new HashMap<>(); - //TODO:gs:c make it possible only to update the changed cmi data. if (ins.size() > 0){ Set <String> set = ins.keySet(); for(Iterator<String> it = set.iterator();it.hasNext();){ @@ -320,30 +306,20 @@ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm synchronized(this) { //o_clusterOK by:fj: instance is spawned by the ScormAPIandDisplayController if(StringHelper.containsNonWhitespace(rawScore)) { scoresProp.put(olatScoId, rawScore); - OutputStream os = null; - try { - os = new BufferedOutputStream(new FileOutputStream(scorePropsFile)); + try(OutputStream os = new BufferedOutputStream(new FileOutputStream(scorePropsFile))) { scoresProp.store(os, null); } catch (IOException e) { throw new OLATRuntimeException(this.getClass(), "could not save scorm-properties-file: "+scorePropsFile.getAbsolutePath(), e); } - finally { - FileUtils.closeSafely(os); - } } if(StringHelper.containsNonWhitespace(lessonStatus)) { lessonStatusProp.put(olatScoId, lessonStatus); - OutputStream os = null; - try { - os = new BufferedOutputStream(new FileOutputStream(lessonStatusPropsFile)); + try(OutputStream os = new BufferedOutputStream(new FileOutputStream(lessonStatusPropsFile))) { lessonStatusProp.store(os, null); } catch (IOException e) { throw new OLATRuntimeException(this.getClass(), "could not save scorm-properties-file: "+scorePropsFile.getAbsolutePath(), e); } - finally { - FileUtils.closeSafely(os); - } } // notify if (!apiCallbacks.isEmpty()) { @@ -421,7 +397,6 @@ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm * @return true if the item is completed */ public boolean isItemCompleted(String itemId){ - //TODO:gs make method faster by caching lmsBean, but when to set out of date? LMSDataFormBean lmsDataBean = new LMSDataFormBean(); lmsDataBean.setItemID(itemId); lmsDataBean.setLmsAction("get"); @@ -435,7 +410,6 @@ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm * @return true if item has any not fullfilled preconditions */ public boolean hasItemPrerequisites(String itemId) { - //TODO:gs make method faster by caching lmsBean, but when to set out of date? LMSDataFormBean lmsDataBean = new LMSDataFormBean(); lmsDataBean.setItemID(itemId); lmsDataBean.setLmsAction("get"); @@ -453,7 +427,7 @@ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm odatahandler = new LMSDataHandler(scormManager, lmsDataBean, scormSettingsHandler); LMSResultsBean lmsBean = odatahandler.getResultsBean(); String[][] preReqTbl = lmsBean.getPreReqTable(); - Map <String,String>itemsStatus = new HashMap<String,String>(); + Map <String,String>itemsStatus = new HashMap<>(); //put table into map for(int i=0; i < preReqTbl.length; i++){ if(preReqTbl[i][1].equals("not attempted")) preReqTbl[i][1] ="not_attempted"; @@ -467,7 +441,6 @@ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm * @return the previos Sco itemId */ public Integer getPreviousSco(String recentId) { - // TODO:gs make method faster by caching lmsBean, but when to set out of date? LMSDataFormBean lmsDataBean = new LMSDataFormBean(); lmsDataBean.setItemID(recentId); lmsDataBean.setLmsAction("get"); @@ -481,7 +454,7 @@ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm break; } } - return new Integer(previousNavScoId); + return Integer.valueOf(previousNavScoId); } /** @@ -489,7 +462,6 @@ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm * @return the next Sco itemId */ public Integer getNextSco(String recentId) { - // TODO:gs make method faster by chaching lmsBean, but when to set out of date? LMSDataFormBean lmsDataBean = new LMSDataFormBean(); lmsDataBean.setItemID(recentId); lmsDataBean.setLmsAction("get"); @@ -503,7 +475,7 @@ public class OLATApiAdapter extends LogDelegator implements ch.ethz.pfplms.scorm break; } } - return new Integer(nextNavScoId); + return Integer.valueOf(nextNavScoId); } /**************************************************************************************** diff --git a/src/main/java/org/olat/modules/scorm/ScormAPIMapper.java b/src/main/java/org/olat/modules/scorm/ScormAPIMapper.java index 3a41494cb6fde67267eee3e222319df64a749b79..b8a4bf6e6dba72addd8f3928993d9b336c5ad6b2 100644 --- a/src/main/java/org/olat/modules/scorm/ScormAPIMapper.java +++ b/src/main/java/org/olat/modules/scorm/ScormAPIMapper.java @@ -49,6 +49,7 @@ import org.olat.core.logging.Tracing; import org.olat.core.util.StringHelper; import org.olat.course.CourseFactory; import org.olat.course.ICourse; +import org.olat.course.assessment.manager.AssessmentNotificationsHandler; import org.olat.course.nodes.ScormCourseNode; import org.olat.course.nodes.scorm.ScormEditController; import org.olat.course.run.scoring.ScoreEvaluation; @@ -199,7 +200,7 @@ public class ScormAPIMapper implements Mapper, ScormAPICallback, Serializable { if (currentPassed == null || !currentPassed.booleanValue()) { // </OLATEE-27> boolean increment = !attemptsIncremented && finish; - ScoreEvaluation sceval = new ScoreEvaluation(new Float(0.0f), Boolean.valueOf(passed)); + ScoreEvaluation sceval = new ScoreEvaluation(Float.valueOf(0.0f), Boolean.valueOf(passed)); scormNode.updateUserScoreEvaluation(sceval, userCourseEnv, identity, increment, Role.user); if(increment) { attemptsIncremented = true; @@ -214,12 +215,17 @@ public class ScormAPIMapper implements Mapper, ScormAPICallback, Serializable { } } else { boolean increment = !attemptsIncremented && finish; - ScoreEvaluation sceval = new ScoreEvaluation(new Float(0.0f), Boolean.valueOf(passed)); + ScoreEvaluation sceval = new ScoreEvaluation(Float.valueOf(0.0f), Boolean.valueOf(passed)); scormNode.updateUserScoreEvaluation(sceval, userCourseEnv, identity, false, Role.user); if(increment) { attemptsIncremented = true; } } + + if(finish) { + Long courseId = userCourseEnv.getCourseEnvironment().getCourseResourceableId(); + CoreSpringFactory.getImpl(AssessmentNotificationsHandler.class).markPublisherNews(identity, courseId); + } if (log.isDebug()) { String msg = "for scorm node:" + scormNode.getIdent() + " (" + scormNode.getShortTitle() + ") a lmsCommit for scoId " @@ -254,7 +260,7 @@ public class ScormAPIMapper implements Mapper, ScormAPICallback, Serializable { if (score > (currentScore != null ? currentScore : -1f)) { // </OLATEE-27> boolean increment = !attemptsIncremented && finish; - ScoreEvaluation sceval = new ScoreEvaluation(new Float(score), Boolean.valueOf(passed)); + ScoreEvaluation sceval = new ScoreEvaluation(Float.valueOf(score), Boolean.valueOf(passed)); scormNode.updateUserScoreEvaluation(sceval, userCourseEnv, identity, increment, Role.user); if(increment) { attemptsIncremented = true; @@ -274,12 +280,17 @@ public class ScormAPIMapper implements Mapper, ScormAPICallback, Serializable { } // </OLATEE-27> boolean increment = !attemptsIncremented && finish; - ScoreEvaluation sceval = new ScoreEvaluation(new Float(score), Boolean.valueOf(passed)); + ScoreEvaluation sceval = new ScoreEvaluation(Float.valueOf(score), Boolean.valueOf(passed)); scormNode.updateUserScoreEvaluation(sceval, userCourseEnv, identity, false, Role.user); if(increment) { attemptsIncremented = true; } } + + if(finish) { + Long courseId = userCourseEnv.getCourseEnvironment().getCourseResourceableId(); + CoreSpringFactory.getImpl(AssessmentNotificationsHandler.class).markPublisherNews(identity, courseId); + } if (log.isDebug()) { String msg = "for scorm node:" + scormNode.getIdent() + " (" + scormNode.getShortTitle() + ") a lmsCommit for scoId " diff --git a/src/test/java/org/olat/selenium/ImsQTI21InteractionsTest.java b/src/test/java/org/olat/selenium/ImsQTI21InteractionsTest.java index 78d8a208a10671916881a9b6763e141192ec6b7d..8fb3833883f2e88c0fb24ace3cc2d70c2fe52c6d 100644 --- a/src/test/java/org/olat/selenium/ImsQTI21InteractionsTest.java +++ b/src/test/java/org/olat/selenium/ImsQTI21InteractionsTest.java @@ -110,7 +110,7 @@ public class ImsQTI21InteractionsTest extends Deployments { .clickToolbarBack() .assertOnAssessmentItem() .answerHotspot("circle") - .saveAnswer() + .saveGraphicAnswer() .assertFeedback("Correct!") .endTest(); //check the results @@ -153,6 +153,7 @@ public class ImsQTI21InteractionsTest extends Deployments { qtiPage .clickToolbarBack() .assertOnAssessmentItem() + .moveToAssociateItems() .answerAssociate("Antonio", 1, true) .answerAssociate("Prospero", 1, false) .answerAssociate("Capulet", 2, true) @@ -201,6 +202,7 @@ public class ImsQTI21InteractionsTest extends Deployments { qtiPage .clickToolbarBack() .assertOnAssessmentItem() + .moveToGraphicAssociateInteraction() .answerGraphicAssociate("B") .answerGraphicAssociate("C") .answerGraphicAssociate("C") @@ -492,6 +494,7 @@ public class ImsQTI21InteractionsTest extends Deployments { qtiPage .clickToolbarBack() .assertOnAssessmentItem() + .moveToGraphicOrderInteraction() .answerGraphicOrderById("A") .answerGraphicOrderById("D") .answerGraphicOrderById("C") @@ -539,9 +542,10 @@ public class ImsQTI21InteractionsTest extends Deployments { qtiPage .clickToolbarBack() .assertOnAssessmentItem() - .answerPositionObject(0, 118, 184) - .answerPositionObject(1, 150, 235) - .answerPositionObject(2, 96, 114) + .moveToPositionObject() + .answerPositionObject(0, 118, 184, 4) + .answerPositionObject(1, 150, 235, 4) + .answerPositionObject(2, 96, 114, 4) .saveAnswer() .endTest() .closeTest(); @@ -629,6 +633,7 @@ public class ImsQTI21InteractionsTest extends Deployments { qtiPage .clickToolbarBack() .assertOnAssessmentItem() + .moveToVerticalSlider() .answerVerticalSlider(16) .saveAnswer() .endTest() diff --git a/src/test/java/org/olat/selenium/page/graphene/Position.java b/src/test/java/org/olat/selenium/page/graphene/Position.java index fd434f254bad8f5d627c38ff9cf9211f7a83b71c..2582fb253d784ec49e45eae3b3d5ae25269a52d8 100644 --- a/src/test/java/org/olat/selenium/page/graphene/Position.java +++ b/src/test/java/org/olat/selenium/page/graphene/Position.java @@ -82,6 +82,16 @@ public class Position { return new Position(x, y); } + public static Position valueOf(int x, int y, int firefoxCorrection, Dimension dimension, WebDriver browser) { + if(browser instanceof FirefoxDriver) { + x = x - Math.round(dimension.getWidth() / 2.0f); + y = y - Math.round(dimension.getHeight() / 2.0f); + x += firefoxCorrection; + y += firefoxCorrection; + } + return new Position(x, y); + } + public static Position valueOf(int x, int y, int width, int height, WebDriver browser) { if(browser instanceof FirefoxDriver) { x = x - Math.round(width / 2.0f); diff --git a/src/test/java/org/olat/selenium/page/qti/QTI21Page.java b/src/test/java/org/olat/selenium/page/qti/QTI21Page.java index 9e65fdb3712212bfbb79d8ed6d9fb9da31bda60d..6c7df79701663a56338b9556cff5491025ef7ce3 100644 --- a/src/test/java/org/olat/selenium/page/qti/QTI21Page.java +++ b/src/test/java/org/olat/selenium/page/qti/QTI21Page.java @@ -181,6 +181,20 @@ public class QTI21Page { return this; } + /** + * Only move if Firefox. + * + * @return Itself + */ + public QTI21Page moveToGraphicOrderInteraction() { + By imgBy = By.xpath("//div[contains(@class,'graphicOrderInteraction')]"); + OOGraphene.waitElement(imgBy, browser); + if(browser instanceof FirefoxDriver) { + OOGraphene.scrollTo(imgBy, browser); + } + return this; + } + /** * Select the area with the specified data-qti-id. * @param id The id save in data-qti-id @@ -189,9 +203,20 @@ public class QTI21Page { public QTI21Page answerGraphicOrderById(String id) { OOGraphene.waitElement(By.className("graphicOrderInteraction"), browser); By areaBy = By.xpath("//div[contains(@class,'graphicOrderInteraction')]//map/area[@data-qti-id='" + id + "']"); - List<WebElement> elements = browser.findElements(areaBy); - Assert.assertEquals("Hotspot with data-qti-id " + id, 1, elements.size()); - elements.get(0).click(); + WebElement areaEl = browser.findElement(areaBy); + if(browser instanceof FirefoxDriver) { + String coords = areaEl.getAttribute("coords"); + By imgBy = By.xpath("//div[contains(@class,'graphicOrderInteraction')]/div/img"); + WebElement element = browser.findElement(imgBy); + Dimension dim = element.getSize(); + Position pos = Position.valueOf(coords, dim, browser); + new Actions(browser) + .moveToElement(element, pos.getX(), pos.getY()) + .click() + .perform(); + } else { + areaEl.click(); + } return this; } @@ -336,25 +361,67 @@ public class QTI21Page { return this; } + /** + * Only move if Firefox. + * + * @return Itself + */ + public QTI21Page moveToAssociateItems() { + By associateItemsBy = By.xpath("//div[@class='association'][3]"); + OOGraphene.waitElement(associateItemsBy, browser); + if(browser instanceof FirefoxDriver) { + OOGraphene.scrollTo(associateItemsBy, browser); + } + return this; + } + public QTI21Page answerGraphicAssociate(String id) { OOGraphene.waitElement(By.className("graphicAssociateInteraction"), browser); By areaBy = By.xpath("//div[contains(@class,'graphicAssociateInteraction')]//map/area[@data-qti-id='" + id + "']"); - List<WebElement> elements = browser.findElements(areaBy); - Assert.assertEquals("Area by " + id, 1, elements.size()); - elements.get(0).click(); + WebElement areaEl = browser.findElement(areaBy); + if(browser instanceof FirefoxDriver) { + String coords = areaEl.getAttribute("coords"); + By imgBy = By.xpath("//div[contains(@class,'graphicAssociateInteraction')]/div/div/img"); + WebElement element = browser.findElement(imgBy); + Dimension dim = element.getSize(); + Position pos = Position.valueOf(coords, dim, browser); + new Actions(browser) + .moveToElement(element, pos.getX(), pos.getY()) + .click() + .perform(); + } else { + areaEl.click(); + } + return this; + } + + /** + * Only move if Firefox. + * + * @return Itself + */ + public QTI21Page moveToGraphicAssociateInteraction() { + By associateItemsBy = By.xpath("//div[@class='graphicAssociateInteraction']"); + OOGraphene.waitElement(associateItemsBy, browser); + if(browser instanceof FirefoxDriver) { + OOGraphene.scrollTo(associateItemsBy, browser); + } return this; } public QTI21Page answerOrderDropItem(String source) { - By sourceBy = By.xpath("//li[contains(@class,'o_assessmentitem_order_item')][text()[contains(.,'" + source + "')]]"); + By sourceBy = By.xpath("//li[@class='o_assessmentitem_order_item'][contains(text(),'" + source + "')]"); OOGraphene.waitElement(sourceBy, 5, browser); WebElement sourceEl = browser.findElement(sourceBy); By targetBy = By.xpath("//div[@class='orderInteraction']//div[contains(@class,'target')]/ul"); WebElement targetEl = browser.findElement(targetBy); + + Position sourcePos = Position.valueOf(30, 30, sourceEl.getSize(), browser); + Position targetPos = Position.valueOf(30, 30, targetEl.getSize(), browser); new Actions(browser) - .moveToElement(sourceEl, 30, 30) + .moveToElement(sourceEl, sourcePos.getX(), sourcePos.getY()) .clickAndHold() - .moveToElement(targetEl, 30, 30) + .moveToElement(targetEl, targetPos.getX(), targetPos.getY()) .release() .build() .perform(); @@ -389,11 +456,23 @@ public class QTI21Page { public QTI21Page answerGraphicGapClick(String item, String gap) { By sourceBy = By.xpath("//div[contains(@class,'gap_container')]/div[contains(@class,'o_gap_item')][@data-qti-id='" + item + "']"); OOGraphene.waitElement(sourceBy, 5, browser); - WebElement sourceEl = browser.findElement(sourceBy); - sourceEl.click(); - By targetBy = By.xpath("//div[@class='graphicGapMatchInteraction']//map/area[@data-qti-id='" + gap + "']"); - WebElement targetEl = browser.findElement(targetBy); - targetEl.click(); + browser.findElement(sourceBy).click(); + By areaBy = By.xpath("//div[@class='graphicGapMatchInteraction']//map/area[@data-qti-id='" + gap + "']"); + WebElement areaEl = browser.findElement(areaBy); + if(browser instanceof FirefoxDriver) { + String coords = areaEl.getAttribute("coords"); + By imgBy = By.xpath("//div[contains(@class,'graphicGapMatchInteraction')]/div/div/img"); + WebElement element = browser.findElement(imgBy); + Dimension dim = element.getSize(); + Position pos = Position.valueOf(coords, dim, browser); + new Actions(browser) + .moveToElement(element, pos.getX(), pos.getY()) + .click() + .perform(); + + } else { + areaEl.click(); + } return this; } @@ -416,31 +495,63 @@ public class QTI21Page { return this; } + /** + * Only move if Firefox. + * + * @return Itself + */ + public QTI21Page moveToPositionObject() { + By itemBy = By.xpath("//div[contains(@class,'positionObjectStage')]//div[contains(@id,'object-item-')]"); + OOGraphene.waitElement(itemBy, browser); + if(browser instanceof FirefoxDriver) { + OOGraphene.scrollTo(itemBy, browser); + } + return this; + } + /** * Select the object by its index (start with 0) and - * move it on the image and the specified coodinates. + * move it on the image and the specified coordinates. * * @param index The index of the object * @param x The x target coordinate * @param y The y target coordinate * @return Itself */ - public QTI21Page answerPositionObject(int index, int x, int y) { + public QTI21Page answerPositionObject(int index, int x, int y, int firefoxCorrection) { By itemBy = By.xpath("//div[contains(@class,'positionObjectStage')]//div[@id='object-item-" + index + "']"); OOGraphene.waitElement(itemBy, browser); WebElement itemEl = browser.findElement(itemBy); By targetBy = By.xpath("//div[@class='positionObjectStage']//img[contains(@id,'qtiworks_id_container_')]"); WebElement targetEl = browser.findElement(targetBy); + Dimension targetDim = targetEl.getSize(); + Position targetPos = Position.valueOf(x, y, firefoxCorrection, targetDim, browser); + Dimension itemDim = itemEl.getSize(); + Position itemPos = Position.valueOf(5, 5, itemDim, browser); new Actions(browser) - .moveToElement(itemEl, 5, 5) + .moveToElement(itemEl, itemPos.getX(), itemPos.getY()) .clickAndHold() - .moveToElement(targetEl, x, y) + .moveToElement(targetEl, targetPos.getX() + 4, targetPos.getY() + 4) .release() .build() .perform(); return this; } + /** + * Only move if Firefox. + * + * @return Itself + */ + public QTI21Page moveToVerticalSlider() { + By interactionBy = By.id("itemBody"); + OOGraphene.waitElement(interactionBy, browser); + if(browser instanceof FirefoxDriver) { + OOGraphene.scrollTo(interactionBy, browser); + } + return this; + } + /** * Select the point based on coordinates. * @@ -453,15 +564,14 @@ public class QTI21Page { WebElement sliderEl = browser.findElement(sliderBy); Dimension size = sliderEl.getSize(); float height = (size.getHeight() / 100f) * val; - float scaledY = size.getHeight() - height; - + int scaledY = Math.round(size.getHeight() - height); + Position pos = Position.valueOf(5, scaledY, size, browser); new Actions(browser) - .moveToElement(sliderEl, 5, Math.round(scaledY)) + .moveToElement(sliderEl, pos.getX(), pos.getY()) .click() .build() .perform(); - By valueBy = By.xpath("//div[contains(@class,'sliderInteraction')]/div[contains(@class,'sliderVertical')]/div[contains(@class,'sliderValue')]/span[text()='" + val + "']"); OOGraphene.waitElement(valueBy, browser); return this;