Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
const assert = require("assert");
 
Feature("Video timeline seek indicator").tag("@regress");
 
Scenario("Seek view should be in sync with indicator position", async ({ I, LabelStudio, AtVideoView, AtPanels }) => {
  const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);
 
  I.amOnPage("/");
  LabelStudio.init({
    config: `
<View>
  <Video name="video" value="$video" />
  <VideoRectangle name="box" toName="video" />
 
  <Labels name="videoLabels" toName="video">
    <Label value="Car" background="#944BFF"/>
    <Label value="Airplane" background="#98C84E"/>
    <Label value="Possum" background="#FA8C16"/>
  </Labels>
</View>`,
    data: { video: "./public/files/opossum_intro.webm" },
  });
 
  I.say("waitForObjectsReady");
  LabelStudio.waitForObjectsReady();
  AtDetailsPanel.collapsePanel();
 
  const trackBbox = await AtVideoView.grabTrackBoundingRect();
  let positionBbox = await AtVideoView.grabPositionBoundingRect();
  let indicatorBbox = await AtVideoView.grabIndicatorBoundingRect();
 
  assert.notDeepEqual({ x: 0, y: 0, width: 0, height: 0 }, trackBbox);
  assert.notDeepEqual({ x: 0, y: 0, width: 0, height: 0 }, positionBbox);
  assert.notDeepEqual({ x: 0, y: 0, width: 0, height: 0 }, indicatorBbox);
 
  const trackZeroX = trackBbox.x;
  const halfway = trackBbox.width / 2 + trackZeroX;
 
  {
    I.say("Drag the video position indicator to the right to the middle");
 
    AtVideoView.drag(positionBbox, halfway, 0);
    positionBbox = await AtVideoView.grabPositionBoundingRect();
    indicatorBbox = await AtVideoView.grabIndicatorBoundingRect();
 
    I.say("Check the video position indicator is close to 50%");
    const delta = Math.round(((positionBbox.x - trackBbox.x) / trackBbox.width) * 10) / 10;
 
    assert.equal(delta, 0.5);
 
    I.say("Check the video position indicator is within the seek indicator");
    assert.ok(
      indicatorBbox.x <= positionBbox.x,
      "Video position indicator not within the seek indicator when dragged near the midway point",
    );
    assert.ok(
      indicatorBbox.x + indicatorBbox.width >= positionBbox.x + positionBbox.width,
      "Video position indicator not within the seek indicator when dragged near the midway point",
    );
  }
 
  {
    I.say("Drag the video position indicator to the end");
    AtVideoView.drag(positionBbox, trackBbox.width + trackBbox.x);
    positionBbox = await AtVideoView.grabPositionBoundingRect();
    indicatorBbox = await AtVideoView.grabIndicatorBoundingRect();
 
    I.say("Check the video position indicator is close to 100%");
    const delta = Math.round(((positionBbox.x - trackBbox.x) / trackBbox.width) * 10) / 10;
 
    assert.equal(delta, 1);
 
    I.say("Check the video position indicator is within the seek indicator");
    assert.ok(
      indicatorBbox.x <= positionBbox.x,
      "Video position indicator not within the seek indicator when dragged to the end",
    );
    assert.ok(
      indicatorBbox.x + indicatorBbox.width >= positionBbox.x + positionBbox.width,
      "Video position indicator not within the seek indicator when dragged to the end",
    );
  }
 
  {
    I.say("Drag the video position indicator to the left to the middle");
 
    AtVideoView.drag(positionBbox, halfway);
    positionBbox = await AtVideoView.grabPositionBoundingRect();
    indicatorBbox = await AtVideoView.grabIndicatorBoundingRect();
 
    I.say("Check the video position indicator is close to 50%");
    const delta = Math.round(((positionBbox.x - trackBbox.x) / trackBbox.width) * 10) / 10;
 
    assert.equal(delta, 0.5);
 
    I.say("Check the video position indicator is within the seek indicator");
    assert.ok(
      indicatorBbox.x <= positionBbox.x,
      "Video position indicator not within the seek indicator when dragged back to the midway point",
    );
    assert.ok(
      indicatorBbox.x + indicatorBbox.width >= positionBbox.x + positionBbox.width,
      "Video position indicator not within the seek indicator when dragged back to the midway point",
    );
  }
 
  {
    I.say("Drag the video position indicator to the start");
    AtVideoView.drag(positionBbox, trackBbox.x, 0);
    positionBbox = await AtVideoView.grabPositionBoundingRect();
    indicatorBbox = await AtVideoView.grabIndicatorBoundingRect();
 
    I.say("Check the video position indicator is close to 0%");
    const delta = Math.round(((positionBbox.x - trackBbox.x) / trackBbox.width) * 10) / 10;
 
    assert.equal(delta, 0);
 
    I.say("Check the video position indicator is within the seek indicator");
    assert.ok(
      indicatorBbox.x <= positionBbox.x,
      "Video position indicator not within the seek indicator when dragged back to the starting point",
    );
    assert.ok(
      indicatorBbox.x + indicatorBbox.width >= positionBbox.x + positionBbox.width,
      "Video position indicator not within the seek indicator when dragged back to the starting point",
    );
  }
 
  {
    I.say("Click on the seek step forward button until the video position indicator is at the end");
    let indicatorPosX = indicatorBbox.x;
 
    I.say("Move the video position indicator to the end of the seek indicator");
    // indicator will have some gaps because of partially hidden last frame, which can't be accessed.
    // 30px is empirically determined to have at least one frame back,
    // so after this we'll go forward step by step until the indicator jumps to the next window
    const endOfSeeker = indicatorBbox.width + indicatorPosX - 30;
    const maxStepsForward = 5;
 
    await AtVideoView.drag(positionBbox, endOfSeeker, 0);
    indicatorBbox = await AtVideoView.grabIndicatorBoundingRect();
 
    I.say("Seeker should not have moved");
    assert.equal(indicatorBbox.x, indicatorPosX, "Seeker should not have moved from this one step movement");
 
    for (let i = 0; i < maxStepsForward; i++) {
      I.say("Click on the seek step forward button");
      await AtVideoView.clickSeekStepForward(1);
      indicatorBbox = await AtVideoView.grabIndicatorBoundingRect();
 
      if (indicatorBbox.x > indicatorPosX) break;
    }
 
    I.say("Seeker should now have moved to the right");
    assert.ok(indicatorBbox.x > indicatorPosX, "Seeker should have moved from these step forward movements");
    indicatorPosX = indicatorBbox.x;
 
    I.say("Click on the seek step backward button");
    await AtVideoView.clickSeekStepBackward(1);
    indicatorBbox = await AtVideoView.grabIndicatorBoundingRect();
 
    I.say("Seeker should now have moved to the left");
    assert.ok(indicatorBbox.x < indicatorPosX, "Seeker should have moved to the left from this one step movement");
  }
})
  .tag("@flakey")
  .retry(3);