Subversion Repositories SmartDukaan

Rev

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

Rev 37436 Rev 37437
Line 125... Line 125...
125
                            String fileName = "/tmp/oppo-simple-i-" + counter + " " + System.currentTimeMillis() + ".png";
125
                            String fileName = "/tmp/oppo-simple-i-" + counter + " " + System.currentTimeMillis() + ".png";
126
                            try {
126
                            try {
127
                                LOGGER.info("Starting Do");
127
                                LOGGER.info("Starting Do");
128
                                wait10Sec.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[starts-with(@id, 'dx_captcha_basic_wrapper_')]")));
128
                                wait10Sec.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[starts-with(@id, 'dx_captcha_basic_wrapper_')]")));
129
                                wait10Sec.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[starts-with(@id, 'dx_captcha_basic_slider_')]")));
129
                                wait10Sec.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[starts-with(@id, 'dx_captcha_basic_slider_')]")));
130
                                slideButton = driver.findElement(By.xpath("//*[starts-with(@id, 'dx_captcha_basic_slider_')]"));
130
                                By sliderBy = By.xpath("//*[starts-with(@id, 'dx_captcha_basic_slider_')]");
131
                                //System.out.println("Slide button - " + slideButton);
131
                                slideButton = driver.findElement(sliderBy);
132
                                LOGGER.info("Margin Left before- " + slideButton.getCssValue("margin-left"));
132
                                LOGGER.info("Margin Left before- " + marginLeftOf(slideButton));
-
 
133
                                try {
-
 
134
                                    actionProvider.moveToElement(slideButton).clickAndHold()
-
 
135
                                            .moveByOffset(1, 0)
-
 
136
                                            .perform();
-
 
137
                                } catch (StaleElementReferenceException stale) {
-
 
138
                                    // The retry refresh (doubleClick at the end of the previous
-
 
139
                                    // attempt) swaps the widget's DOM, so the handle located at the
-
 
140
                                    // top of THIS attempt can already be detached by the time we act
-
 
141
                                    // on it. Re-find and try once, rather than abandoning the imei
-
 
142
                                    // and reloading the page: this killed 19% of oppo attempts
-
 
143
                                    // (54 of 284), against 0 on realme.
-
 
144
                                    LOGGER.info("Slider went stale before the drag - re-finding and retrying once");
-
 
145
                                    slideButton = driver.findElement(sliderBy);
133
                                actionProvider.moveToElement(slideButton).clickAndHold()
146
                                    actionProvider.moveToElement(slideButton).clickAndHold()
134
                                        .moveByOffset(1, 0)//.pause(2000)
147
                                            .moveByOffset(1, 0)
135
                                        .perform();
148
                                            .perform();
-
 
149
                                }
136
                                pressed = true;
150
                                pressed = true;
137
 
-
 
138
                                //actionProvider.moveByOffset(-1,0).pause(2000).perform();
-
 
139
                                LOGGER.info("Margin Left after - " + slideButton.getCssValue("margin-left"));
151
                                LOGGER.info("Margin Left after - " + marginLeftOf(slideButton));
140
                                //Thread.sleep(5000);
152
                                //Thread.sleep(5000);
141
                            } catch (Exception e) {
153
                            } catch (Exception e) {
142
                                LOGGER.warn("Slider captcha widget (dx_captcha_basic_*) not found or not draggable"
154
                                LOGGER.warn("Slider captcha widget (dx_captcha_basic_*) not found or not draggable"
143
                                        + " - reloading. Cause: " + e.getMessage());
155
                                        + " - reloading. Cause: " + e.getMessage());
144
                                driver.get("https://support.oppo.com/in/warranty-check/");
156
                                driver.get("https://support.oppo.com/in/warranty-check/");
Line 150... Line 162...
150
                                double firstCircleX = circles[0];
162
                                double firstCircleX = circles[0];
151
                                double secondCircleX = circles[1];
163
                                double secondCircleX = circles[1];
152
                                double distance = Math.abs(firstCircleX - secondCircleX);
164
                                double distance = Math.abs(firstCircleX - secondCircleX);
153
                                LOGGER.info("Distance is " + distance);
165
                                LOGGER.info("Distance is " + distance);
154
 
166
 
155
                                double[] cal = calibrate(driver, actionProvider, distance);
167
                                // Closed loop: no assumed ratio, every move re-measures.
156
                                int moveByOffset = cal == null ? 0 : (int) cal[0];
168
                                int moveByOffset = aim(driver, actionProvider, distance);
157
                                if (moveByOffset > 0) {
169
                                if (moveByOffset > 0) {
158
                                    // Move, then measure again and close the remainder BEFORE
-
 
159
                                    // releasing. Releasing on the first computed offset committed
-
 
160
                                    // the whole calibration error to the final position.
-
 
161
                                    actionProvider.moveByOffset(moveByOffset, 0).perform();
-
 
162
                                    refine(driver, actionProvider, cal[1]);
-
 
163
                                    actionProvider.release().perform();
170
                                    actionProvider.release().perform();
164
                                    pressed = false;
171
                                    pressed = false;
165
                                    LOGGER.info("Move click performed (main {}px)", moveByOffset);
172
                                    LOGGER.info("Move click performed (main {}px)", moveByOffset);
166
                                    try {
173
                                    try {
167
                                        try {
174
                                        try {
Line 395... Line 402...
395
     *
402
     *
396
     * Aim to close about half the gap: the piece travels roughly ASSUMED_RATIO px per
403
     * Aim to close about half the gap: the piece travels roughly ASSUMED_RATIO px per
397
     * slider px, so nudge ~= gap / (2 * ratio), clamped. The real ratio is still derived
404
     * slider px, so nudge ~= gap / (2 * ratio), clamped. The real ratio is still derived
398
     * from the measurement; this only sizes the probe.
405
     * from the measurement; this only sizes the probe.
399
     */
406
     */
400
    /*
-
 
401
     * These are OPPO's tuning values and are deliberately independent of realme's.
-
 
402
     * The two classes drive the same dingxiang widget but convert very differently
-
 
403
     * (oppo ~12%, realme ~60% of completed drags), so a change proven on one is NOT
-
 
404
     * evidence for the other. Tune and measure them separately.
-
 
405
     *
-
 
406
     * Back to 1.7 after r37433 set this to 0.67 and it did not help (14% -> 13%).
-
 
407
     *
-
 
408
     * 0.67 was fitted to 3,694 production calibrations -- but every one of those was
-
 
409
     * measured while the slider press was being leaked between attempts (r37435), so the
-
 
410
     * sample was contaminated and the value fitted to noise.
-
 
411
     *
-
 
412
     * With that confound removed the clean evidence is realme: identical widget, same
-
 
413
     * calibration code, ASSUMED_RATIO 1.7, converting 82%. Measured post-fix on similar
-
 
414
     * gaps, 1.7 probes ~31px and closes ~11% of the gap, while 0.67 probes ~52px and
-
 
415
     * closes ~37%. The SMALLER probe is the one that lands -- a probe that eats a third
-
 
416
     * of the gap risks crossing the target during calibration, and |c1-c2| cannot tell
-
 
417
     * "not there yet" from "gone past", so the derived ratio comes back wrong.
-
 
418
     */
-
 
419
    private static final double ASSUMED_RATIO = 1.7;
-
 
420
    private static final int MIN_NUDGE = 10;
-
 
421
    private static final int MAX_NUDGE = 60;
-
 
422
    private static final double ALIGNED_PX = 2.0;
407
    private static final double ALIGNED_PX = 2.0;
423
 
408
 
424
    private static int nudgeFor(double gap) {
-
 
425
        int n = (int) Math.round(gap / (2 * ASSUMED_RATIO));
-
 
426
        return Math.max(MIN_NUDGE, Math.min(MAX_NUDGE, n));
-
 
427
    }
-
 
428
 
-
 
429
    /**
409
    /**
430
     * @return {sliderPxToMove, sliderPxPerCirclePx}, or null when the circles could not
410
     * margin-left for logging only. Reading it off a detached element throws, and this is
431
     *         be read. Returned rather than stored on a field: this is a singleton and
411
     * a diagnostic -- it must never be the reason an attempt is abandoned.
432
     *         the Oppo secondary and tertiary jobs run it concurrently.
-
 
433
     */
412
     */
434
    private double[] calibrate(WebDriver driver, Actions actionProvider, double distance) throws Exception {
-
 
435
        int nudge = nudgeFor(distance);
-
 
436
        actionProvider.moveByOffset(nudge, 0).perform();
413
    private String marginLeftOf(WebElement el) {
437
 
-
 
438
        String fileName2 = "/tmp/" + Thread.currentThread().getName() + "-moved.png";
-
 
439
        double[] movedCircles = getMatCircles2(driver, fileName2);
-
 
440
        if (movedCircles == null) return null;
414
        try {
441
 
-
 
442
        double movedDistance = Math.abs(movedCircles[0] - movedCircles[1]);
-
 
443
        double closed = distance - movedDistance;
415
            return el.getCssValue("margin-left");
444
        LOGGER.info("Calibration: gap {} -> {} after {}px, closed {}",
-
 
445
                distance, movedDistance, nudge, closed);
416
        } catch (Exception e) {
446
 
-
 
447
        // A non-positive value means the piece did not move, or moved away from the
-
 
448
        // hole -- either way the ratio is meaningless and guessing would waste the try.
-
 
449
        if (closed <= 0) return null;
417
            return "?";
450
 
-
 
451
        double sliderPerCircle = nudge / closed;
418
        }
452
        int moveBy = (int) Math.round(movedDistance * sliderPerCircle);
-
 
453
        return new double[]{moveBy, sliderPerCircle};
-
 
454
    }
419
    }
455
 
420
 
-
 
421
    /** Seed probe, in slider px. Deliberately small: it only has to produce a first
-
 
422
     *  measurable movement, and it must be too small to cross the hole. */
-
 
423
    private static final int SEED_NUDGE = 12;
-
 
424
    private static final int MAX_AIM_STEPS = 5;
-
 
425
    private static final int MAX_STEP = 140;
-
 
426
 
456
    /**
427
    /**
-
 
428
     * Drag onto the hole without ever assuming a ratio.
-
 
429
     *
-
 
430
     * The old path derived px-per-px ONCE from a single probe and then trusted it for
-
 
431
     * the main move and the correction alike, so one noisy probe poisoned everything
-
 
432
     * downstream. Here every move is also a measurement: after each one the ratio is
457
     * Measure again after the main move and close whatever is left. The old code
433
     * re-derived from what that move actually achieved, so the estimate improves as the
458
     * committed its single computed offset blind, so any calibration error went
434
     * piece approaches instead of being fixed at its least informed point.
-
 
435
     *
-
 
436
     * It also gets to see overshoot, which a single pass cannot. The gap is |c1 - c2|,
-
 
437
     * so it cannot tell "not there yet" from "gone past" -- but if a move makes the gap
459
     * straight into the final position with nothing to catch it.
438
     * GROW, we crossed the hole, and the sign of the next step flips.
-
 
439
     *
-
 
440
     * @return total slider px moved, or 0 if the circles could not be read at all.
460
     */
441
     */
461
    private int refine(WebDriver driver, Actions actionProvider, double sliderPerCircle) throws Exception {
442
    private int aim(WebDriver driver, Actions actionProvider, double gap) throws Exception {
-
 
443
        double ratio = 0;               // slider px per gap px, derived from real moves
-
 
444
        int total = 0;
-
 
445
        int step = SEED_NUDGE;
-
 
446
 
-
 
447
        for (int i = 0; i < MAX_AIM_STEPS; i++) {
-
 
448
            actionProvider.moveByOffset(step, 0).perform();
-
 
449
            total += step;
-
 
450
 
462
        String fileName = "/tmp/" + Thread.currentThread().getName() + "-refine.png";
451
            double[] c = getMatCircles2(driver, "/tmp/" + Thread.currentThread().getName() + "-aim" + i + ".png");
-
 
452
            if (c == null) {
-
 
453
                LOGGER.warn("Aim step {}: lost the circles after {}px", i, total);
-
 
454
                return total;
-
 
455
            }
463
        double[] circles = getMatCircles2(driver, fileName);
456
            double now = Math.abs(c[0] - c[1]);
464
        if (circles == null) return 0;
457
            double closed = gap - now;
465
 
458
 
-
 
459
            if (closed != 0) {
-
 
460
                // What this move actually bought us, in slider px per gap px.
466
        double remaining = Math.abs(circles[0] - circles[1]);
461
                double observed = Math.abs(step) / Math.abs(closed);
-
 
462
                // Blend once an estimate exists, so a single bad frame cannot swing it.
-
 
463
                ratio = (ratio <= 0) ? observed : (ratio + observed) / 2;
-
 
464
            }
-
 
465
            LOGGER.info("Aim step {}: moved {}px, gap {} -> {}, ratio now {}",
-
 
466
                    i, step, gap, now, String.format("%.2f", ratio));
-
 
467
 
467
        if (remaining <= ALIGNED_PX) return 0;
468
            if (now <= ALIGNED_PX) {
-
 
469
                LOGGER.info("Aim: aligned within {}px after {} step(s), {}px total", ALIGNED_PX, i + 1, total);
-
 
470
                return total;
-
 
471
            }
-
 
472
            if (ratio <= 0) {                       // nothing measurable yet, probe again
-
 
473
                gap = now;
-
 
474
                step = SEED_NUDGE;
-
 
475
                continue;
-
 
476
            }
468
 
477
 
469
        int correction = (int) Math.round(remaining * sliderPerCircle);
478
            int next = (int) Math.round(now * ratio);
470
        if (correction == 0) return 0;
479
            if (next == 0) next = 1;
-
 
480
            if (next > MAX_STEP) next = MAX_STEP;
471
        LOGGER.info("Refine: {}px of gap left -> nudging {}px", remaining, correction);
481
            // Gap grew => we are past the hole; go back instead of further away.
472
        actionProvider.moveByOffset(correction, 0).perform();
482
            step = (closed < 0) ? -next : next;
-
 
483
            gap = now;
-
 
484
        }
-
 
485
        LOGGER.info("Aim: {} steps used, {}px total", MAX_AIM_STEPS, total);
473
        return correction;
486
        return total;
474
    }
487
    }
475
 
488
 
-
 
489
 
-
 
490
 
476
    private Mat getMatCircles(WebDriver driver, String fileName) throws Exception {
491
    private Mat getMatCircles(WebDriver driver, String fileName) throws Exception {
477
 
492
 
478
        File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
493
        File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
479
        BufferedImage fullImg = ImageIO.read(screenshot);
494
        BufferedImage fullImg = ImageIO.read(screenshot);
480
 
495