Subversion Repositories SmartDukaan

Rev

Rev 37437 | Rev 37439 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 37437 Rev 37438
Line 162... Line 162...
162
                                double firstCircleX = circles[0];
162
                                double firstCircleX = circles[0];
163
                                double secondCircleX = circles[1];
163
                                double secondCircleX = circles[1];
164
                                double distance = Math.abs(firstCircleX - secondCircleX);
164
                                double distance = Math.abs(firstCircleX - secondCircleX);
165
                                LOGGER.info("Distance is " + distance);
165
                                LOGGER.info("Distance is " + distance);
166
 
166
 
167
                                // Closed loop: no assumed ratio, every move re-measures.
167
                                // Closed loop on the ring's own position; no assumed ratio.
168
                                int moveByOffset = aim(driver, actionProvider, distance);
168
                                int moveByOffset = aim(driver, actionProvider, circles);
169
                                if (moveByOffset > 0) {
169
                                if (moveByOffset > 0) {
170
                                    actionProvider.release().perform();
170
                                    actionProvider.release().perform();
171
                                    pressed = false;
171
                                    pressed = false;
172
                                    LOGGER.info("Move click performed (main {}px)", moveByOffset);
172
                                    LOGGER.info("Move click performed (main {}px)", moveByOffset);
173
                                    try {
173
                                    try {
Line 422... Line 422...
422
     *  measurable movement, and it must be too small to cross the hole. */
422
     *  measurable movement, and it must be too small to cross the hole. */
423
    private static final int SEED_NUDGE = 12;
423
    private static final int SEED_NUDGE = 12;
424
    private static final int MAX_AIM_STEPS = 5;
424
    private static final int MAX_AIM_STEPS = 5;
425
    private static final int MAX_STEP = 140;
425
    private static final int MAX_STEP = 140;
426
 
426
 
-
 
427
    /** Centre of the draggable ring, in page CSS px, or null if it is not there. */
-
 
428
    private Double pieceCentreX(WebDriver driver) {
-
 
429
        try {
-
 
430
            WebElement piece = driver.findElement(By.xpath("//*[starts-with(@id, 'dx_captcha_basic_sub-slider_')]"));
-
 
431
            return piece.getLocation().getX() + (piece.getSize().getWidth() / 2.0);
-
 
432
        } catch (Exception e) {
-
 
433
            return null;
-
 
434
        }
-
 
435
    }
-
 
436
 
427
    /**
437
    /**
428
     * Drag onto the hole without ever assuming a ratio.
438
     * Drag the ring onto the hole, deriving px-per-px as we go.
-
 
439
     *
-
 
440
     * Why this does not re-run circle detection: it cannot. Hough needs to resolve TWO
-
 
441
     * circles of similar radius, and as the ring closes on the hole they overlap and stop
-
 
442
     * being two -- so the measurement dies exactly at convergence. Measured in production
-
 
443
     * after r37437: 25 of 36 attempts lost the circles on the very step after the main
-
 
444
     * move, and NOT ONE ever reported aligned. The old refine() hit the same wall and
-
 
445
     * silently released. Any loop built on "distance between two detected circles" is
-
 
446
     * blind at the only moment that matters.
-
 
447
     *
-
 
448
     * So the target is fixed ONCE, from the detection already done while the two are far
-
 
449
     * apart and resolvable, and from then on we track the RING'S OWN ELEMENT, which stays
-
 
450
     * readable however close it gets. Verified locally against the live widget: the ring's
-
 
451
     * rect tracks the drag cleanly the whole way in (+10 mouse -> +11 ring, repeatably).
429
     *
452
     *
430
     * The old path derived px-per-px ONCE from a single probe and then trusted it for
453
     * The circle x's are already CSS px relative to the bg element -- the crop in
431
     * the main move and the correction alike, so one noisy probe poisoned everything
454
     * getMatCircles2 uses CSS coordinates and chrome runs with
432
     * downstream. Here every move is also a measurement: after each one the ratio is
-
 
433
     * re-derived from what that move actually achieved, so the estimate improves as the
455
     * --force-device-scale-factor=1 -- so the only conversion needed is the bg offset.
434
     * piece approaches instead of being fixed at its least informed point.
-
 
435
     *
456
     *
436
     * It also gets to see overshoot, which a single pass cannot. The gap is |c1 - c2|,
457
     * Every move is also a measurement: the ratio is re-derived from what that move
437
     * so it cannot tell "not there yet" from "gone past" -- but if a move makes the gap
458
     * actually achieved, and it is signed, so overshoot corrects itself instead of needing
438
     * GROW, we crossed the hole, and the sign of the next step flips.
459
     * to be detected.
439
     *
460
     *
440
     * @return total slider px moved, or 0 if the circles could not be read at all.
461
     * @return total slider px moved, or 0 if the ring could not be located at all.
441
     */
462
     */
442
    private int aim(WebDriver driver, Actions actionProvider, double gap) throws Exception {
463
    private int aim(WebDriver driver, Actions actionProvider, double[] circleXs) throws Exception {
-
 
464
        Double piece = pieceCentreX(driver);
-
 
465
        if (piece == null) {
-
 
466
            LOGGER.warn("Aim: no ring element to track");
-
 
467
            return 0;
-
 
468
        }
-
 
469
        int bgX;
-
 
470
        try {
-
 
471
            bgX = driver.findElement(By.xpath("//*[starts-with(@id, 'dx_captcha_basic_bg_')]")).getLocation().getX();
-
 
472
        } catch (Exception e) {
-
 
473
            LOGGER.warn("Aim: bg element gone - " + e.getMessage());
-
 
474
            return 0;
-
 
475
        }
-
 
476
 
-
 
477
        // Of the two detected circles the ring is whichever sits at the ring element; the
-
 
478
        // other one is the hole we are aiming at.
-
 
479
        double a = bgX + circleXs[0], b = bgX + circleXs[1];
-
 
480
        double target = (Math.abs(a - piece) <= Math.abs(b - piece)) ? b : a;
-
 
481
        LOGGER.info("Aim: ring at {} target at {} ({}px to close)",
-
 
482
                Math.round(piece), Math.round(target), Math.round(target - piece));
-
 
483
 
443
        double ratio = 0;               // slider px per gap px, derived from real moves
484
        double ratio = 0;               // ring px per mouse px, signed, derived from real moves
444
        int total = 0;
485
        int total = 0;
445
        int step = SEED_NUDGE;
486
        int step = SEED_NUDGE;
446
 
487
 
447
        for (int i = 0; i < MAX_AIM_STEPS; i++) {
488
        for (int i = 0; i < MAX_AIM_STEPS; i++) {
448
            actionProvider.moveByOffset(step, 0).perform();
489
            actionProvider.moveByOffset(step, 0).perform();
449
            total += step;
490
            total += step;
450
 
491
 
451
            double[] c = getMatCircles2(driver, "/tmp/" + Thread.currentThread().getName() + "-aim" + i + ".png");
492
            Double now = pieceCentreX(driver);
452
            if (c == null) {
493
            if (now == null) {
453
                LOGGER.warn("Aim step {}: lost the circles after {}px", i, total);
494
                LOGGER.warn("Aim step {}: lost the ring after {}px", i, total);
454
                return total;
495
                return total;
455
            }
496
            }
456
            double now = Math.abs(c[0] - c[1]);
-
 
457
            double closed = gap - now;
497
            double moved = now - piece;
458
 
-
 
459
            if (closed != 0) {
498
            if (Math.abs(moved) > 0.5) {
460
                // What this move actually bought us, in slider px per gap px.
-
 
461
                double observed = Math.abs(step) / Math.abs(closed);
499
                double observed = moved / step;
462
                // Blend once an estimate exists, so a single bad frame cannot swing it.
-
 
463
                ratio = (ratio <= 0) ? observed : (ratio + observed) / 2;
500
                ratio = (ratio == 0) ? observed : (ratio + observed) / 2;
464
            }
501
            }
-
 
502
            double remaining = target - now;
465
            LOGGER.info("Aim step {}: moved {}px, gap {} -> {}, ratio now {}",
503
            LOGGER.info("Aim step {}: mouse {}px -> ring moved {}px, {}px left, ratio {}",
466
                    i, step, gap, now, String.format("%.2f", ratio));
504
                    i, step, Math.round(moved), Math.round(remaining), String.format("%.2f", ratio));
467
 
505
 
468
            if (now <= ALIGNED_PX) {
506
            if (Math.abs(remaining) <= ALIGNED_PX) {
469
                LOGGER.info("Aim: aligned within {}px after {} step(s), {}px total", ALIGNED_PX, i + 1, total);
507
                LOGGER.info("Aim: aligned to {}px after {} step(s), {}px of slider",
-
 
508
                        Math.round(remaining), i + 1, total);
470
                return total;
509
                return total;
471
            }
510
            }
-
 
511
            piece = now;
472
            if (ratio <= 0) {                       // nothing measurable yet, probe again
512
            if (ratio == 0) {           // nothing measurable yet, probe again
473
                gap = now;
-
 
474
                step = SEED_NUDGE;
513
                step = SEED_NUDGE;
475
                continue;
514
                continue;
476
            }
515
            }
477
 
-
 
478
            int next = (int) Math.round(now * ratio);
516
            int next = (int) Math.round(remaining / ratio);
479
            if (next == 0) next = 1;
517
            if (next == 0) next = (remaining > 0 ? 1 : -1);
480
            if (next > MAX_STEP) next = MAX_STEP;
518
            if (next > MAX_STEP) next = MAX_STEP;
481
            // Gap grew => we are past the hole; go back instead of further away.
-
 
482
            step = (closed < 0) ? -next : next;
519
            if (next < -MAX_STEP) next = -MAX_STEP;
483
            gap = now;
520
            step = next;
484
        }
521
        }
485
        LOGGER.info("Aim: {} steps used, {}px total", MAX_AIM_STEPS, total);
522
        LOGGER.info("Aim: {} steps used, {}px of slider, still short", MAX_AIM_STEPS, total);
486
        return total;
523
        return total;
487
    }
524
    }
488
 
525
 
489
 
-
 
490
 
-
 
491
    private Mat getMatCircles(WebDriver driver, String fileName) throws Exception {
526
    private Mat getMatCircles(WebDriver driver, String fileName) throws Exception {
492
 
527
 
493
        File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
528
        File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
494
        BufferedImage fullImg = ImageIO.read(screenshot);
529
        BufferedImage fullImg = ImageIO.read(screenshot);
495
 
530